From b23be4ea4d5aed6e1473505233501b14d871a8ec Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Fri, 16 Oct 2026 12:13:47 +0200 Subject: [PATCH 01/49] GdiPlus test project: 579 cases in five areas, every NOTE and WARNING marked (1158 of 1158 as documented, win32 and win64); GdiPlus leaves PENDING; claims the cases contradicted corrected (no call hangs: each is a stack overflow or an access violation, checked in an IDE run and an exe; RedEyeCorrection, Save, ConvertFormat, baseInset, GdiplusStartupInput, Color); new WARNINGs: CColor.DarkSeaGreen, NotificationHook/Unhook, RedEyeCorrection parameters with areas --- .../Reference/Contributed/GdiPlus/ArrowCap.md | 13 +- docs/Reference/Contributed/GdiPlus/Bitmap.md | 14 +- docs/Reference/Contributed/GdiPlus/Blur.md | 1 + .../Contributed/GdiPlus/BrightnessContrast.md | 1 + .../Contributed/GdiPlus/CachedBitmap.md | 1 + .../Contributed/GdiPlus/ColorBalance.md | 2 + .../Contributed/GdiPlus/ColorCurve.md | 2 + .../Reference/Contributed/GdiPlus/ColorLUT.md | 5 +- .../Contributed/GdiPlus/ColorMatrixEffect.md | 2 + .../Contributed/GdiPlus/CustomLineCap.md | 18 +- docs/Reference/Contributed/GdiPlus/Effect.md | 2 + .../GdiPlus/Enumerations/CColor.md | 18 +- .../GdiPlus/Enumerations/ColorMask.md | 1 + .../Enumerations/GdiplusStartupParams.md | 1 + .../GdiPlus/Enumerations/GpCompositingMode.md | 1 + .../Enumerations/GpInterpolationMode.md | 1 + .../GdiPlus/Enumerations/GpSmoothingMode.md | 1 + .../GdiPlus/Enumerations/GpStatus.md | 1 + .../Enumerations/GpTextRenderingHint.md | 1 + docs/Reference/Contributed/GdiPlus/Font.md | 11 + .../Contributed/GdiPlus/FontCollection.md | 1 + .../Contributed/GdiPlus/FontFamily.md | 5 + .../Contributed/GdiPlus/GdiPlusAbort.md | 1 + .../Contributed/GdiPlus/GdiPlusBase.md | 5 + .../Contributed/GdiPlus/GdiPlusUser.md | 4 + .../Reference/Contributed/GdiPlus/Graphics.md | 18 +- .../Contributed/GdiPlus/GraphicsPath.md | 38 + .../Contributed/GdiPlus/HatchBrush.md | 6 +- docs/Reference/Contributed/GdiPlus/Helpers.md | 3 + .../GdiPlus/HueSaturationLightness.md | 2 + docs/Reference/Contributed/GdiPlus/IBrush.md | 7 + .../Contributed/GdiPlus/ITransformable.md | 3 +- docs/Reference/Contributed/GdiPlus/Image.md | 18 +- .../Contributed/GdiPlus/ImageAttributes.md | 2 + .../Contributed/GdiPlus/ImageCodec.md | 3 + .../GdiPlus/InstalledFontCollection.md | 1 + docs/Reference/Contributed/GdiPlus/Levels.md | 3 + .../GdiPlus/LinearGradientBrush.md | 18 +- docs/Reference/Contributed/GdiPlus/Matrix.md | 3 + .../Reference/Contributed/GdiPlus/Metafile.md | 2 + .../Reference/Contributed/GdiPlus/PathData.md | 12 + .../Contributed/GdiPlus/PathGradientBrush.md | 26 +- .../Contributed/GdiPlus/PathIterator.md | 26 +- docs/Reference/Contributed/GdiPlus/Pen.md | 23 +- .../GdiPlus/PrivateFontCollection.md | 1 + .../Contributed/GdiPlus/RedEyeCorrection.md | 12 +- docs/Reference/Contributed/GdiPlus/Sharpen.md | 1 + .../Contributed/GdiPlus/SolidBrush.md | 4 + .../Contributed/GdiPlus/StringFormat.md | 7 + .../Contributed/GdiPlus/TextureBrush.md | 12 + docs/Reference/Contributed/GdiPlus/Tint.md | 1 + .../Contributed/GdiPlus/Types/Color.md | 42 +- .../GdiPlus/Types/ColorLUTParams.md | 1 + .../Contributed/GdiPlus/Types/ColorMatrix.md | 1 + .../Contributed/GdiPlus/Types/ColorPalette.md | 1 + .../GdiPlus/Types/GdiplusStartupInput.md | 8 +- .../GdiPlus/Types/GdiplusStartupOutput.md | 16 +- .../GdiPlus/Types/GpEncoderParameter.md | 1 + .../GdiPlus/Types/GpImageCodecInfo.md | 1 + .../Contributed/GdiPlus/Types/GpPoint.md | 25 + .../Contributed/GdiPlus/Types/GpPointF.md | 36 + .../GdiPlus/Types/GpPropertyItem.md | 2 + .../Contributed/GdiPlus/Types/GpRect.md | 45 + .../Contributed/GdiPlus/Types/GpRectF.md | 48 + .../Contributed/GdiPlus/Types/GpSize.md | 21 + .../Contributed/GdiPlus/Types/GpSizeF.md | 28 + .../Contributed/GdiPlus/Types/LevelsParams.md | 1 + .../GdiPlus/Types/PathPointType.md | 5 + .../GdiPlus/Types/RedEyeCorrectionParams.md | 3 +- .../Contributed/GdiPlus/VBGraphics.md | 5 + docs/Reference/Contributed/GdiPlus/index.md | 11 +- scripts/check_package_tests.mjs | 2 +- .../GdiPlus/Sources/Br_BrushColourTests.twin | 201 ++ .../GdiPlus/Sources/Br_BrushTests.twin | 312 +++ .../GdiPlus/Sources/Br_CapTests.twin | 258 ++ .../Sources/Br_GradientBrushTests.twin | 405 +++ .../GdiPlus/Sources/Br_Helpers.twin | 68 + .../GdiPlus/Sources/Br_PathDataTests.twin | 217 ++ .../GdiPlus/Sources/Br_PathIteratorTests.twin | 281 +++ .../GdiPlus/Sources/Br_PathTests.twin | 385 +++ .../GdiPlus/Sources/Br_PenTests.twin | 359 +++ .../GdiPlus/Sources/Fx_EffectTests.twin | 534 ++++ .../Sources/Fx_FontCollectionTests.twin | 173 ++ .../GdiPlus/Sources/Fx_FontTests.twin | 167 ++ .../GdiPlus/Sources/Fx_Helpers.twin | 38 + .../GdiPlus/Sources/Fx_StringFormatTests.twin | 288 +++ .../GdiPlus/Sources/Gr_BaseTests.twin | 126 + .../GdiPlus/Sources/Gr_GraphicsTests.twin | 462 ++++ .../GdiPlus/Sources/Gr_Helpers.twin | 69 + .../GdiPlus/Sources/Gr_LibraryTests.twin | 246 ++ .../GdiPlus/Sources/Gr_MatrixTests.twin | 179 ++ .../GdiPlus/Sources/Gr_NeverAbort.twin | 8 + .../GdiPlus/Sources/Gr_VBGraphicsTests.twin | 184 ++ .../GdiPlus/Sources/Im_AttributeTests.twin | 96 + .../GdiPlus/Sources/Im_BitmapTests.twin | 152 ++ .../GdiPlus/Sources/Im_CachedBitmapTests.twin | 42 + .../GdiPlus/Sources/Im_CodecTests.twin | 108 + .../GdiPlus/Sources/Im_Files.twin | 124 + .../GdiPlus/Sources/Im_ImageTests.twin | 281 +++ .../GdiPlus/Sources/Im_MetafileTests.twin | 167 ++ .../GdiPlus/Sources/Im_TypeTests.twin | 202 ++ .../GdiPlus/Sources/Im_ZStackTests.twin | 19 + .../package-tests/GdiPlus/Sources/RunAll.twin | 2186 +++++++++++++++++ .../GdiPlus/Sources/Ty_CColorTests.twin | 271 ++ .../GdiPlus/Sources/Ty_ColorTests.twin | 445 ++++ .../GdiPlus/Sources/Ty_GpPointFTests.twin | 247 ++ .../GdiPlus/Sources/Ty_GpPointTests.twin | 172 ++ .../GdiPlus/Sources/Ty_GpRectFTests.twin | 327 +++ .../GdiPlus/Sources/Ty_GpRectTests.twin | 307 +++ .../GdiPlus/Sources/Ty_GpSizeFTests.twin | 196 ++ .../GdiPlus/Sources/Ty_GpSizeTests.twin | 136 + .../GdiPlus/Sources/Ty_StartupTests.twin | 160 ++ .../GdiPlus/Sources/Ty_Support.twin | 49 + .../GdiPlus/compile/Fx_CompileTests.twin | 13 + .../GdiPlus/compile/Gr_CompileTests.twin | 53 + .../GdiPlus/compile/Im_CompileTests.twin | 34 + .../GdiPlus/compile/Ty_CompileTests.twin | 20 + test/package-tests/GdiPlus/expect.json | 643 +++++ 118 files changed, 12034 insertions(+), 44 deletions(-) create mode 100644 test/package-tests/GdiPlus/Sources/Br_BrushColourTests.twin create mode 100644 test/package-tests/GdiPlus/Sources/Br_BrushTests.twin create mode 100644 test/package-tests/GdiPlus/Sources/Br_CapTests.twin create mode 100644 test/package-tests/GdiPlus/Sources/Br_GradientBrushTests.twin create mode 100644 test/package-tests/GdiPlus/Sources/Br_Helpers.twin create mode 100644 test/package-tests/GdiPlus/Sources/Br_PathDataTests.twin create mode 100644 test/package-tests/GdiPlus/Sources/Br_PathIteratorTests.twin create mode 100644 test/package-tests/GdiPlus/Sources/Br_PathTests.twin create mode 100644 test/package-tests/GdiPlus/Sources/Br_PenTests.twin create mode 100644 test/package-tests/GdiPlus/Sources/Fx_EffectTests.twin create mode 100644 test/package-tests/GdiPlus/Sources/Fx_FontCollectionTests.twin create mode 100644 test/package-tests/GdiPlus/Sources/Fx_FontTests.twin create mode 100644 test/package-tests/GdiPlus/Sources/Fx_Helpers.twin create mode 100644 test/package-tests/GdiPlus/Sources/Fx_StringFormatTests.twin create mode 100644 test/package-tests/GdiPlus/Sources/Gr_BaseTests.twin create mode 100644 test/package-tests/GdiPlus/Sources/Gr_GraphicsTests.twin create mode 100644 test/package-tests/GdiPlus/Sources/Gr_Helpers.twin create mode 100644 test/package-tests/GdiPlus/Sources/Gr_LibraryTests.twin create mode 100644 test/package-tests/GdiPlus/Sources/Gr_MatrixTests.twin create mode 100644 test/package-tests/GdiPlus/Sources/Gr_NeverAbort.twin create mode 100644 test/package-tests/GdiPlus/Sources/Gr_VBGraphicsTests.twin create mode 100644 test/package-tests/GdiPlus/Sources/Im_AttributeTests.twin create mode 100644 test/package-tests/GdiPlus/Sources/Im_BitmapTests.twin create mode 100644 test/package-tests/GdiPlus/Sources/Im_CachedBitmapTests.twin create mode 100644 test/package-tests/GdiPlus/Sources/Im_CodecTests.twin create mode 100644 test/package-tests/GdiPlus/Sources/Im_Files.twin create mode 100644 test/package-tests/GdiPlus/Sources/Im_ImageTests.twin create mode 100644 test/package-tests/GdiPlus/Sources/Im_MetafileTests.twin create mode 100644 test/package-tests/GdiPlus/Sources/Im_TypeTests.twin create mode 100644 test/package-tests/GdiPlus/Sources/Im_ZStackTests.twin create mode 100644 test/package-tests/GdiPlus/Sources/RunAll.twin create mode 100644 test/package-tests/GdiPlus/Sources/Ty_CColorTests.twin create mode 100644 test/package-tests/GdiPlus/Sources/Ty_ColorTests.twin create mode 100644 test/package-tests/GdiPlus/Sources/Ty_GpPointFTests.twin create mode 100644 test/package-tests/GdiPlus/Sources/Ty_GpPointTests.twin create mode 100644 test/package-tests/GdiPlus/Sources/Ty_GpRectFTests.twin create mode 100644 test/package-tests/GdiPlus/Sources/Ty_GpRectTests.twin create mode 100644 test/package-tests/GdiPlus/Sources/Ty_GpSizeFTests.twin create mode 100644 test/package-tests/GdiPlus/Sources/Ty_GpSizeTests.twin create mode 100644 test/package-tests/GdiPlus/Sources/Ty_StartupTests.twin create mode 100644 test/package-tests/GdiPlus/Sources/Ty_Support.twin create mode 100644 test/package-tests/GdiPlus/compile/Fx_CompileTests.twin create mode 100644 test/package-tests/GdiPlus/compile/Gr_CompileTests.twin create mode 100644 test/package-tests/GdiPlus/compile/Im_CompileTests.twin create mode 100644 test/package-tests/GdiPlus/compile/Ty_CompileTests.twin create mode 100644 test/package-tests/GdiPlus/expect.json diff --git a/docs/Reference/Contributed/GdiPlus/ArrowCap.md b/docs/Reference/Contributed/GdiPlus/ArrowCap.md index ff490ca6..4ba6dfd4 100644 --- a/docs/Reference/Contributed/GdiPlus/ArrowCap.md +++ b/docs/Reference/Contributed/GdiPlus/ArrowCap.md @@ -34,7 +34,9 @@ Each form can also be written with **New**: *cap* **= New ArrowCap**( ... ). The *isFilled* : *optional* A **Boolean**: **True** for a filled arrowhead and **False** for an outline. The default is **True**. + Note that the height comes before the width. Both are multiples of the pen width, and a pen narrower than 2 counts as 2. A new arrow cap has the base cap **LineCapTriangle**. + ### Clone @@ -42,6 +44,7 @@ Syntax: *copy* **=** *cap*.**Clone**( ) Returns a new **ArrowCap** with the same settings. + > [!WARNING] 0.9.45.0 > The copy is not usable: it does not hold the arrow, so each of its properties reports **InvalidParameter** in **LastResult** and returns 0 or **False**. Make a second cap with the same values instead. @@ -71,6 +74,7 @@ Syntax: *cap*.**MiddleInset** [ **=** *inset* ] Sets the distance, a **Single**, by which the middle of the base of the arrowhead is moved towards the tip. The default is 0, which leaves a plain triangle. A larger value cuts a notch into the base, like the tail of a dart. + > [!WARNING] 0.9.45.0 > Reading **MiddleInset** always returns 0, whatever was set. The setting itself works. @@ -80,6 +84,7 @@ Syntax: *cap*.**FillState** [ **=** *isFilled* ] Gets or sets whether the arrowhead is filled, a **Boolean**. When it is not, only the outline is drawn. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() @@ -106,16 +111,21 @@ Debug.Print cap.MiddleInset ' 9 8 ' 0 ``` + ## Inherited from CustomLineCap -[**BaseCap**](CustomLineCap#basecap), [**BaseInset**](CustomLineCap#baseinset), [**WidthScale**](CustomLineCap#widthscale), [**StrokeJoin**](CustomLineCap#strokejoin), [**StrokeCap**](CustomLineCap#strokecap), [**StartStrokeCap**](CustomLineCap#startstrokecap), [**EndStrokeCap**](CustomLineCap#endstrokecap), [**SetStrokeCaps**](CustomLineCap#setstrokecaps) and [**GetStrokeCaps**](CustomLineCap#getstrokecaps) work as on that page, with the same warnings: assigning **StartStrokeCap** or **EndStrokeCap**, and calling **SetStrokeCaps** with a cap, never return. + +[**BaseCap**](CustomLineCap#basecap), [**BaseInset**](CustomLineCap#baseinset), [**WidthScale**](CustomLineCap#widthscale), [**StrokeJoin**](CustomLineCap#strokejoin), [**StrokeCap**](CustomLineCap#strokecap), [**StartStrokeCap**](CustomLineCap#startstrokecap), [**EndStrokeCap**](CustomLineCap#endstrokecap), [**SetStrokeCaps**](CustomLineCap#setstrokecaps) and [**GetStrokeCaps**](CustomLineCap#getstrokecaps) work as on that page, with the same warnings: assigning **StartStrokeCap** or **EndStrokeCap**, and calling **SetStrokeCaps** with a cap, stop the program with a stack overflow. + + > [!WARNING] 0.9.45.0 > A pen that has an arrow cap returns it from [**CustomStartCap**](Pen#customstartcap) or [**CustomEndCap**](Pen#customendcap) as an **ArrowCap** that is not usable, like a clone: its members report **InvalidParameter** and return 0. ## Example + A filled arrowhead at the end of a line. The listing shows the columns 20 to 35. **#** is a pixel of the pen. ```tb check_build check_run @@ -149,6 +159,7 @@ Next ' ................ ' ................ ``` + ## See Also diff --git a/docs/Reference/Contributed/GdiPlus/Bitmap.md b/docs/Reference/Contributed/GdiPlus/Bitmap.md index 63986096..b4db077d 100644 --- a/docs/Reference/Contributed/GdiPlus/Bitmap.md +++ b/docs/Reference/Contributed/GdiPlus/Bitmap.md @@ -76,6 +76,7 @@ Each form is also available as **New Bitmap**( ... ) with the same arguments. A constructor that fails creates an unusable object whose **LastResult** tells why: a resource that does not exist gives **InvalidParameter**. + > [!WARNING] 0.9.45.0 > Two integer arguments do not create a bitmap of that size. `Bitmap(8, 6)` binds to the form that takes a GDI bitmap handle and a palette handle, treats 8 and 6 as handles, and returns an unusable object with **LastResult** 7 (**Win32Error**), without any error being raised. Pass **Single** values: `Bitmap(8!, 6!)`, or give a format, as in `Bitmap(8, 6, PixelFormat32bppARGB)`. The same goes for **New Bitmap**. @@ -137,6 +138,7 @@ Syntax: *copy* **=** *bmp*.**Clone**( *x*, *y*, *width*, *height*, *format* ) Returns a new **Bitmap** with the pixels of that part, converted to *format*. The rectangle must lie inside the bitmap. The copy does not share memory with the original. + > [!NOTE] 0.9.45.0 > The no-argument **Clone** of [**Image**](Image#clone) is hidden: `bmp.Clone()` on a **Bitmap** does not compile. Copy the whole bitmap by giving its rectangle. @@ -256,15 +258,16 @@ Syntax: *status* **=** *bmp*.**ConvertFormat**( *format*, *ditherType*, *palette : *required* A [**PaletteType**](Enumerations/PaletteType) for a conversion to an indexed format. *palette* -: *required* A [**ColorPalette**](Types/ColorPalette) that supplies the colours when *paletteType* is **PaletteTypeCustom**, and receives them for some other types. +: *required* A [**ColorPalette**](Types/ColorPalette) that supplies the colours of the converted bitmap. Fill in its **count** and **entries** whatever *paletteType* is. *alphaThresholdPercent* : *required* A **Single** from 0 to 100: the alpha value, in percent, below which a pixel becomes transparent in a format that has only on-off transparency. Converts the bitmap itself, in place, to the new pixel format. It needs GDI+ 1.1 (Windows 7 or later). A conversion that GDI+ refuses returns an error and leaves the bitmap as it was. -> [!NOTE] 0.9.45.0 -> The fixed palette types, such as **PaletteTypeFixedBW**, need a *palette* that GDI+ has prepared. A blank [**ColorPalette**](Types/ColorPalette) makes the call fail with **InvalidParameter**. A palette that the program fills in itself works with **PaletteTypeCustom**, as below. + +> [!WARNING] 0.9.45.0 +> The call always hands GDI+ the *palette* it is given, so *paletteType* does not make GDI+ choose the colours: **PaletteTypeFixedBW**, **PaletteTypeOptimal** and the other types work only with a palette whose **count** and **entries** the program has filled in. With a blank [**ColorPalette**](Types/ColorPalette) the call returns **Ok**, the converted bitmap has a palette with no usable colours, and its pixels come out black (**PaletteTypeFixedBW**) or transparent (**PaletteTypeOptimal**). The sample below fills in a palette of two colours for **PaletteTypeCustom**. ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() @@ -407,9 +410,11 @@ Debug.Print Hex$(flat.Pixel(1, 1).argb) A bitmap saves with [**Image.Save**](Image#save), **SaveAdd** and **SaveAddImage**, which a **Bitmap** inherits. + > [!WARNING] 0.9.45.0 -> **Save** does not work with the encoder parameter array that it requires: the call returns **InvalidParameter** and leaves an empty file for an array of one element, ignores the parameters for a longer one in a 64-bit program, and stops a 32-bit program. The warning at [**Image.Save**](Image#save) gives the details. +> **Save** does not work with the encoder parameter array that it requires. In a 64-bit program the call returns **InvalidParameter** and leaves an empty file for an array of one element, and ignores the parameters of a longer one. A 32-bit program stops with an access violation. The warning at [**Image.Save**](Image#save) gives the details. + A picture can still be saved without parameters by passing a GDI bitmap made with [**GetHBitmap**](#gethbitmap) to GDI+ and calling its save function directly. The module below does that for a PNG. It flattens the picture over white, because the intermediate GDI bitmap has no alpha channel. ```tb check_build slot=file projname=gdiplus-bitmap-save @@ -462,6 +467,7 @@ Debug.Print Hex$(loaded.Pixel(3, 3).argb) ' 0 16 8 ' FFC86432 ``` + ## Example diff --git a/docs/Reference/Contributed/GdiPlus/Blur.md b/docs/Reference/Contributed/GdiPlus/Blur.md index 800ca85d..3651fff3 100644 --- a/docs/Reference/Contributed/GdiPlus/Blur.md +++ b/docs/Reference/Contributed/GdiPlus/Blur.md @@ -43,6 +43,7 @@ Syntax: *params* **=** *fx*.**Parameters**( *size* ) Gets or sets the [**BlurParams**](Types/BlurParams). + > [!WARNING] 0.9.45.0 > The getter returns zeros and sets **LastResult** to 2 (**InvalidParameter**) unless it is given *size*, the size of the structure: `fx.Parameters(LenB(Of BlurParams))`. See [**Effect**](Effect#parameters). diff --git a/docs/Reference/Contributed/GdiPlus/BrightnessContrast.md b/docs/Reference/Contributed/GdiPlus/BrightnessContrast.md index 03f06fcf..105a0426 100644 --- a/docs/Reference/Contributed/GdiPlus/BrightnessContrast.md +++ b/docs/Reference/Contributed/GdiPlus/BrightnessContrast.md @@ -43,6 +43,7 @@ Syntax: *params* **=** *fx*.**Parameters**( *size* ) Gets or sets the [**BrightnessContrastParams**](Types/BrightnessContrastParams). + > [!WARNING] 0.9.45.0 > The getter returns zeros and sets **LastResult** to 2 (**InvalidParameter**) unless it is given *size*, the size of the structure: `fx.Parameters(LenB(Of BrightnessContrastParams))`. See [**Effect**](Effect#parameters). diff --git a/docs/Reference/Contributed/GdiPlus/CachedBitmap.md b/docs/Reference/Contributed/GdiPlus/CachedBitmap.md index 912cb317..f4efd907 100644 --- a/docs/Reference/Contributed/GdiPlus/CachedBitmap.md +++ b/docs/Reference/Contributed/GdiPlus/CachedBitmap.md @@ -25,6 +25,7 @@ Syntax: *cached* **= New CachedBitmap**( *bitmap*, *gr* ) A constructor that fails creates an unusable object whose **LastResult** tells why. + > [!NOTE] 0.9.45.0 > A **CachedBitmap** is valid for the **Graphics** it was made for, and for another with the same pixel format. If the format of the destination changes (for example, the display resolution or colour depth changes), the cached copy has to be made again, and drawing it on a destination with another format fails with **WrongState**. diff --git a/docs/Reference/Contributed/GdiPlus/ColorBalance.md b/docs/Reference/Contributed/GdiPlus/ColorBalance.md index f7618f40..f59a52ae 100644 --- a/docs/Reference/Contributed/GdiPlus/ColorBalance.md +++ b/docs/Reference/Contributed/GdiPlus/ColorBalance.md @@ -38,6 +38,7 @@ Each form is also available as **New ColorBalance**( ... ) with the same argumen A value of 0 changes nothing. A constructor that fails creates an unusable object whose **LastResult** tells why. + > [!WARNING] 0.9.45.0 > The form with *params* stops the program with an access violation, in 32-bit and 64-bit programs. The forms without arguments and with the three values work. To use a structure, create the effect without arguments and set [**Parameters**](#parameters). @@ -49,6 +50,7 @@ Syntax: *params* **=** *fx*.**Parameters**( *size* ) Gets or sets the [**ColorBalanceParams**](Types/ColorBalanceParams). + > [!WARNING] 0.9.45.0 > The getter returns zeros and sets **LastResult** to 2 (**InvalidParameter**) unless it is given *size*, the size of the structure: `fx.Parameters(LenB(Of ColorBalanceParams))`. See [**Effect**](Effect#parameters). diff --git a/docs/Reference/Contributed/GdiPlus/ColorCurve.md b/docs/Reference/Contributed/GdiPlus/ColorCurve.md index 38e42d78..6d2827d2 100644 --- a/docs/Reference/Contributed/GdiPlus/ColorCurve.md +++ b/docs/Reference/Contributed/GdiPlus/ColorCurve.md @@ -38,6 +38,7 @@ Each form is also available as **New ColorCurve**( ... ) with the same arguments A constructor that fails creates an unusable object whose **LastResult** tells why. + > [!WARNING] 0.9.45.0 > The form with *params* stops the program with an access violation, in 32-bit and 64-bit programs. The forms without arguments and with the three values work. To use a structure, create the effect without arguments and set [**Parameters**](#parameters). @@ -49,6 +50,7 @@ Syntax: *params* **=** *fx*.**Parameters**( *size* ) Gets or sets the [**ColorCurveParams**](Types/ColorCurveParams). + > [!WARNING] 0.9.45.0 > The getter returns zeros and sets **LastResult** to 2 (**InvalidParameter**) unless it is given *size*, the size of the structure: `fx.Parameters(LenB(Of ColorCurveParams))`. See [**Effect**](Effect#parameters). diff --git a/docs/Reference/Contributed/GdiPlus/ColorLUT.md b/docs/Reference/Contributed/GdiPlus/ColorLUT.md index f425315d..80cd4114 100644 --- a/docs/Reference/Contributed/GdiPlus/ColorLUT.md +++ b/docs/Reference/Contributed/GdiPlus/ColorLUT.md @@ -32,11 +32,13 @@ Each form is also available as **New ColorLUT**( ... ) with the same arguments. A constructor that fails creates an unusable object whose **LastResult** tells why. + > [!WARNING] 0.9.45.0 > The form with *params* stops the program with an access violation, in 32-bit and 64-bit programs. Create the effect without arguments and set [**Parameters**](#parameters). + > [!WARNING] 0.9.45.0 -> The form with four arrays copies the alpha table from the array *lutB*, not from *lutA*, so the alpha channel is mapped through the blue table. With the blue table of the example below, the result is transparent. Fill the four tables of a [**ColorLUTParams**](Types/ColorLUTParams) yourself and set **Parameters**. +> The form with four arrays copies the alpha table from the array *lutB*, not from *lutA*, so the alpha channel is mapped through the blue table. With a blue table that inverts, as in the example below, an opaque picture becomes transparent. Fill the four tables of a [**ColorLUTParams**](Types/ColorLUTParams) yourself and set **Parameters**. ## Parameters @@ -46,6 +48,7 @@ Syntax: *params* **=** *fx*.**Parameters**( *size* ) Gets or sets the [**ColorLUTParams**](Types/ColorLUTParams). + > [!WARNING] 0.9.45.0 > The getter returns zeros and sets **LastResult** to 2 (**InvalidParameter**) unless it is given *size*, the size of the structure: `fx.Parameters(LenB(Of ColorLUTParams))`. See [**Effect**](Effect#parameters). diff --git a/docs/Reference/Contributed/GdiPlus/ColorMatrixEffect.md b/docs/Reference/Contributed/GdiPlus/ColorMatrixEffect.md index 300f06be..6642c55e 100644 --- a/docs/Reference/Contributed/GdiPlus/ColorMatrixEffect.md +++ b/docs/Reference/Contributed/GdiPlus/ColorMatrixEffect.md @@ -27,6 +27,7 @@ Each form is also available as **New ColorMatrixEffect**( ... ) with the same ar A constructor that fails creates an unusable object whose **LastResult** tells why. The effect created without arguments has the default matrix of GDI+. + > [!WARNING] 0.9.45.0 > The form with *params* stops the program with an access violation, in 32-bit and 64-bit programs. Create the effect without arguments and set [**Parameters**](#parameters). @@ -38,6 +39,7 @@ Syntax: *params* **=** *fx*.**Parameters**( *size* ) Gets or sets the [**ColorMatrix**](Types/ColorMatrix). + > [!WARNING] 0.9.45.0 > The getter returns zeros and sets **LastResult** to 2 (**InvalidParameter**) unless it is given *size*, the size of the structure: `fx.Parameters(LenB(Of ColorMatrix))`. See [**Effect**](Effect#parameters). diff --git a/docs/Reference/Contributed/GdiPlus/CustomLineCap.md b/docs/Reference/Contributed/GdiPlus/CustomLineCap.md index cff99904..950f169f 100644 --- a/docs/Reference/Contributed/GdiPlus/CustomLineCap.md +++ b/docs/Reference/Contributed/GdiPlus/CustomLineCap.md @@ -34,9 +34,11 @@ Syntax: *cap* **= New CustomLineCap**( *fillPath*, *strokePath* [, *baseCap* [, The two forms are one thing. The paths use their own coordinate system, with its origin at the end of the line. The x axis runs across the line. The y axis runs along it: the negative y values are back along the line, towards the other end, and the positive y values lie beyond the end of the line. The cap is scaled by the width of the pen, and a pen narrower than 2 counts as 2. + > [!NOTE] 0.9.45.0 -> *baseInset* is a **Long**, so a fraction is cut off. The [**BaseInset**](#baseinset) property takes a **Single**. +> *baseInset* is a **Long**, so a fraction is rounded to a whole number. The [**BaseInset**](#baseinset) property takes a **Single**. + The example draws a line that ends in a small filled rectangle. The path is two units wide and one unit long, behind the end of the line, and the pen is 2 wide: the cap is 4 rows tall and 2 columns long. **#** is a pixel of the pen. ```tb check_build check_run @@ -73,6 +75,7 @@ Next ' .............................. ' .............................. ``` + ### Clone @@ -118,8 +121,9 @@ Syntax: *lineCap* **=** *cap*.**StartStrokeCap** Reads the [**GpLineCap**](Enumerations/GpLineCap) of the start of each line of the stroke path. The default is **LineCapFlat**. + > [!WARNING] 0.9.45.0 -> Assigning **StartStrokeCap** never returns. Assign [**StrokeCap**](#strokecap) to set both ends. +> Assigning **StartStrokeCap** stops the program with a stack overflow. Assign [**StrokeCap**](#strokecap) to set both ends. ### EndStrokeCap @@ -127,8 +131,9 @@ Syntax: *lineCap* **=** *cap*.**EndStrokeCap** Reads the [**GpLineCap**](Enumerations/GpLineCap) of the end of each line of the stroke path. The default is **LineCapFlat**. + > [!WARNING] 0.9.45.0 -> Assigning **EndStrokeCap** never returns. Assign [**StrokeCap**](#strokecap) to set both ends. +> Assigning **EndStrokeCap** stops the program with a stack overflow. Assign [**StrokeCap**](#strokecap) to set both ends. ### SetStrokeCaps @@ -136,8 +141,9 @@ Syntax: *status* **=** *cap*.**SetStrokeCaps**( [ *startCap* [, *endCap*]] ) Sets the start cap and the end cap of the stroke lines separately, and returns a [**GpStatus**](Enumerations/GpStatus). A cap that is left out, or given as -1, is not changed. + > [!WARNING] 0.9.45.0 -> **SetStrokeCaps** never returns, unless both caps are left out. Assign [**StrokeCap**](#strokecap) to set both ends. +> **SetStrokeCaps** stops the program with a stack overflow, unless both caps are left out. Assign [**StrokeCap**](#strokecap) to set both ends. ### GetStrokeCaps @@ -145,6 +151,7 @@ Syntax: *status* **=** *cap*.**GetStrokeCaps**( [ *startCap* [, *endCap*]] ) Reads the start cap and the end cap into the variables given, and returns a [**GpStatus**](Enumerations/GpStatus). A variable that is left out is not read. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() @@ -171,9 +178,11 @@ Debug.Print cap.StartStrokeCap & " " & cap.EndStrokeCap ' 0 2 2 ' 2 2 ``` + ## Example + A line that stops short of its cap: the base inset ends the line 3 pen widths early, and the cap stays where it was. ```tb check_build check_run @@ -206,6 +215,7 @@ Debug.Print copy.BaseInset ' ..############....##.......... ' 3 ``` + ## See Also diff --git a/docs/Reference/Contributed/GdiPlus/Effect.md b/docs/Reference/Contributed/GdiPlus/Effect.md index 5b8f28a7..fb53c62a 100644 --- a/docs/Reference/Contributed/GdiPlus/Effect.md +++ b/docs/Reference/Contributed/GdiPlus/Effect.md @@ -50,6 +50,7 @@ Syntax: *params* **=** *effect*.**Parameters**( *size* ) The setter replaces the parameters. The getter reads them, and needs *size*, the size of the parameters structure in bytes (`LenB(Of BlurParams)`). + > [!WARNING] 0.9.45.0 > Without *size*, the getter returns a structure of zeros and sets **LastResult** to 2 (**InvalidParameter**), because GDI+ is given a buffer size of 0. Pass the size every time: `blur.Parameters(LenB(Of BlurParams)).radius`. @@ -102,6 +103,7 @@ Syntax: *n* **=** *effect*.**ParameterSize** Returns the size in bytes of the parameters that the effect takes, as GDI+ states it. + > [!WARNING] 0.9.45.0 > **ParameterSize** always returns 0. The property assigns the status of the call, not the size, to its return value. Use `LenB(Of BlurParams)` and the like instead. diff --git a/docs/Reference/Contributed/GdiPlus/Enumerations/CColor.md b/docs/Reference/Contributed/GdiPlus/Enumerations/CColor.md index 7ce9d1d2..c4d0daa4 100644 --- a/docs/Reference/Contributed/GdiPlus/Enumerations/CColor.md +++ b/docs/Reference/Contributed/GdiPlus/Enumerations/CColor.md @@ -10,16 +10,29 @@ permalink: /tB/Packages/Contributed/GdiPlus/Enumerations/CColor Names 140 common colours, and one fully transparent colour, as 32-bit ARGB values. -This enumeration belongs to the package; GDI+ has no counterpart. Each member is a **Long** in the layout of a [**Color**](../Types/Color): alpha in the highest byte, then red, green and blue. Every member is opaque (alpha 255), except **Transparent**. The names and the red, green and blue values are those of the named colours of HTML and CSS. +This enumeration belongs to the package; GDI+ has no counterpart. Each member is a **Long** in the layout of a [**Color**](../Types/Color): alpha in the highest byte, then red, green and blue. Every member is opaque (alpha 255), except **Transparent**. + +The names and the red, green and blue values are those of the named colours of HTML and CSS, except those of **DarkSeaGreen**. + + + A member can be written with or without the name of the enumeration: `White` or `CColor.White`. The exact way to use one is to give it to the **Color** function, as in `Color(CColor.Orange)`, which keeps all four bytes. + + + +> [!WARNING] 0.9.45.0 +> **DarkSeaGreen** is `&HFF8FBC8B`, which is `#8FBC8B`. The colour of that name in HTML and CSS is `#8FBC8F`, so the blue channel is 4 less than it should be. + > [!WARNING] 0.9.45.0 > A member passed straight to a method that has an overload taking a **Long** (the *rgb* parameters of **SolidBrush**, **Pen**, **Graphics.Clear** and others) binds to that overload, which reads the value as `&H00RRGGBB` and sets the alpha to 255. The red, green and blue of an opaque member come out right, but the alpha is lost: `SolidBrush(CColor.Transparent)` is opaque white, not transparent. Write `SolidBrush(Color(CColor.Transparent))` instead. + > [!NOTE] 0.9.45.0 > A **CColor** member is not a VB colour. A VB colour constant such as **vbRed**, and the result of **RGB**, put red in the lowest byte and have no alpha. The **BGR** property of a **Color** returns its value in that layout. + | Member | Value | Description | |:-------|:------|:------------| | **AliceBlue**{: #AliceBlue } | `&HFFF0F8FF` | Opaque, `#F0F8FF`. | @@ -55,7 +68,7 @@ A member can be written with or without the name of the enumeration: `White` or | **DarkOrchid**{: #DarkOrchid } | `&HFF9932CC` | Opaque, `#9932CC`. | | **DarkRed**{: #DarkRed } | `&HFF8B0000` | Opaque, `#8B0000`. | | **DarkSalmon**{: #DarkSalmon } | `&HFFE9967A` | Opaque, `#E9967A`. | -| **DarkSeaGreen**{: #DarkSeaGreen } | `&HFF8FBC8B` | Opaque, `#8FBC8B`. | +| **DarkSeaGreen**{: #DarkSeaGreen } | `&HFF8FBC8B` | Opaque, `#8FBC8B` (CSS: `#8FBC8F`). | | **DarkSlateBlue**{: #DarkSlateBlue } | `&HFF483D8B` | Opaque, `#483D8B`. | | **DarkSlateGray**{: #DarkSlateGray } | `&HFF2F4F4F` | Opaque, `#2F4F4F`. | | **DarkTurquoise**{: #DarkTurquoise } | `&HFF00CED1` | Opaque, `#00CED1`. | @@ -163,6 +176,7 @@ A member can be written with or without the name of the enumeration: `White` or | **WhiteSmoke**{: #WhiteSmoke } | `&HFFF5F5F5` | Opaque, `#F5F5F5`. | | **Yellow**{: #Yellow } | `&HFFFFFF00` | Opaque, `#FFFF00`. | | **YellowGreen**{: #YellowGreen } | `&HFF9ACD32` | Opaque, `#9ACD32`. | + ## Used by diff --git a/docs/Reference/Contributed/GdiPlus/Enumerations/ColorMask.md b/docs/Reference/Contributed/GdiPlus/Enumerations/ColorMask.md index 342a46c5..c6ef8ee2 100644 --- a/docs/Reference/Contributed/GdiPlus/Enumerations/ColorMask.md +++ b/docs/Reference/Contributed/GdiPlus/Enumerations/ColorMask.md @@ -12,6 +12,7 @@ Gives the bit mask that selects a channel of an ARGB value. Each mask is `&HFF` shifted left by the matching member of [ColorShift](ColorShift). No public member takes or returns this enumeration. + > [!NOTE] 0.9.45.0 > **AlphaMask** has the sign bit set, so shifting the masked value right by **AlphaShift** copies the sign bit and gives -1 for an alpha of 255. Mask the result with `&HFF` again, or use the **Alpha** property of a [**Color**](../Types/Color). diff --git a/docs/Reference/Contributed/GdiPlus/Enumerations/GdiplusStartupParams.md b/docs/Reference/Contributed/GdiPlus/Enumerations/GdiplusStartupParams.md index 14c550e3..a120a65e 100644 --- a/docs/Reference/Contributed/GdiPlus/Enumerations/GdiplusStartupParams.md +++ b/docs/Reference/Contributed/GdiPlus/Enumerations/GdiplusStartupParams.md @@ -12,6 +12,7 @@ Specifies options for the start-up of GDI+. GDI+ version 1 ignores these values. They apply when the **GdiplusVersion** of the [**GdiplusStartupInput**](../Types/GdiplusStartupInput) is 2 or higher. The default input of the **GdiPlusUser** constructor function uses **GdiplusStartupDefault**. + > [!NOTE] 0.9.45.0 > **GdiplusStartupReserved1** and **GdiplusStartupReserved2** have the values 5 and 6, because the package gives no value to them and they follow **GdiplusStartupReserved0**. They are not single bits. diff --git a/docs/Reference/Contributed/GdiPlus/Enumerations/GpCompositingMode.md b/docs/Reference/Contributed/GdiPlus/Enumerations/GpCompositingMode.md index 54bf6a45..947bfa7d 100644 --- a/docs/Reference/Contributed/GdiPlus/Enumerations/GpCompositingMode.md +++ b/docs/Reference/Contributed/GdiPlus/Enumerations/GpCompositingMode.md @@ -12,6 +12,7 @@ Selects how a drawn colour is combined with the colour already on the surface. The setting is the **CompositingMode** property of [**Graphics**](../Graphics). It matters only for colours that are not fully opaque. + > [!NOTE] timeless > Text drawn with [**TextRenderingHintClearTypeGridFit**](GpTextRenderingHint) cannot be combined with **CompositingModeSourceCopy**. This is a limit of GDI+. diff --git a/docs/Reference/Contributed/GdiPlus/Enumerations/GpInterpolationMode.md b/docs/Reference/Contributed/GdiPlus/Enumerations/GpInterpolationMode.md index 625d041c..ce4883ff 100644 --- a/docs/Reference/Contributed/GdiPlus/Enumerations/GpInterpolationMode.md +++ b/docs/Reference/Contributed/GdiPlus/Enumerations/GpInterpolationMode.md @@ -12,6 +12,7 @@ Specifies the algorithm that is used when images are scaled or rotated. The setting is the **InterpolationMode** property of [**Graphics**](../Graphics). The first four members have the values of [GpQualityMode](GpQualityMode). + > [!NOTE] timeless > GDI+ replaces some of the values when they are set. A **Graphics** object that was given **InterpolationModeHighQuality** returns **InterpolationModeHighQualityBicubic** when the property is read again, and one that was given **InterpolationModeLowQuality** or **InterpolationModeDefault** returns **InterpolationModeBilinear**. diff --git a/docs/Reference/Contributed/GdiPlus/Enumerations/GpSmoothingMode.md b/docs/Reference/Contributed/GdiPlus/Enumerations/GpSmoothingMode.md index c32d201c..d2b3b10f 100644 --- a/docs/Reference/Contributed/GdiPlus/Enumerations/GpSmoothingMode.md +++ b/docs/Reference/Contributed/GdiPlus/Enumerations/GpSmoothingMode.md @@ -12,6 +12,7 @@ Specifies the kind of smoothing (antialiasing) that is applied to lines and curv The setting is the **SmoothingMode** property of [**Graphics**](../Graphics). A box filter that is 8 pixels wide and 4 high gives better results for lines that are nearly vertical than for lines that are nearly horizontal. A filter of 8 by 8 pixels gives equally good results for both and is slower. The first four members have the values of [GpQualityMode](GpQualityMode). + > [!NOTE] timeless > GDI+ replaces some of the values when they are set. A **Graphics** object that was given **SmoothingModeHighQuality** returns **SmoothingModeAntiAlias** when the property is read again, and one that was given **SmoothingModeHighSpeed** or **SmoothingModeDefault** returns **SmoothingModeNone**. diff --git a/docs/Reference/Contributed/GdiPlus/Enumerations/GpStatus.md b/docs/Reference/Contributed/GdiPlus/Enumerations/GpStatus.md index 5fe003a7..e863c786 100644 --- a/docs/Reference/Contributed/GdiPlus/Enumerations/GpStatus.md +++ b/docs/Reference/Contributed/GdiPlus/Enumerations/GpStatus.md @@ -12,6 +12,7 @@ Indicates the result of a GDI+ call. Nearly every method of the package that does not return a value returns a **GpStatus**, and every object keeps the last one in its **LastResult** property of [**GdiPlusBase**](../GdiPlusBase). What happens to a status other than **Ok** depends on the error handling strategy, [GpErrorHandlingStrategy](GpErrorHandlingStrategy). **AsText** returns the text of a status. + > [!NOTE] timeless > Creating an image from a file that does not exist gives **InvalidParameter**, not **FileNotFound**. diff --git a/docs/Reference/Contributed/GdiPlus/Enumerations/GpTextRenderingHint.md b/docs/Reference/Contributed/GdiPlus/Enumerations/GpTextRenderingHint.md index a02f2d2a..fc38b5bb 100644 --- a/docs/Reference/Contributed/GdiPlus/Enumerations/GpTextRenderingHint.md +++ b/docs/Reference/Contributed/GdiPlus/Enumerations/GpTextRenderingHint.md @@ -12,6 +12,7 @@ Specifies the process used to render text, which affects the quality of the text A process that gives higher quality is generally slower than one that gives lower quality. **TextRenderingHintClearTypeGridFit** gives the best quality for most LCD monitors and relatively small font sizes. **TextRenderingHintAntiAlias** gives the best quality for rotated text. + > [!NOTE] timeless > **TextRenderingHintClearTypeGridFit** cannot be used together with [**CompositingModeSourceCopy**](GpCompositingMode). diff --git a/docs/Reference/Contributed/GdiPlus/Font.md b/docs/Reference/Contributed/GdiPlus/Font.md index 040392d9..b4334d07 100644 --- a/docs/Reference/Contributed/GdiPlus/Font.md +++ b/docs/Reference/Contributed/GdiPlus/Font.md @@ -12,6 +12,7 @@ Describes a font by its family, size, style and unit, for drawing and measuring A **Font** object is what [**Graphics.DrawString**](Graphics) and [**Graphics.MeasureString**](Graphics) take to draw and measure text. It holds one [**FontFamily**](FontFamily), a size, a style such as bold or italic, and the unit of the size. A **Font** is read-only after it is created: to change the size or the style, create another one. The class mirrors the GDI+ [Font](https://learn.microsoft.com/en-us/windows/win32/api/gdiplusheaders/nl-gdiplusheaders-font) class. + > [!NOTE] 0.9.45.0 > In a project that references OLE Automation, which is the default, **Font** is also the name of a type of that library. A variable declared **As Font** therefore holds the OLE type, and assigning a GDI+ font to it raises run-time error -2147467262, *No such interface supported*. Declare the variable **As GdiPlus.Font**. The function **Font**( ) in the examples below resolves to the package without a prefix. A **New Font**( ... ) expression does not compile without the prefix either (TB5074), so write **New GdiPlus.Font**( ... ). @@ -41,6 +42,7 @@ Syntax: *font* **= Font**( *familyName*, *emSize* [, *style* [, *unit* [, *fontC *fontCollection* : *optional* A [**FontCollection**](FontCollection) in which to look for the family. When it is omitted, the installed fonts are searched. + A *familyName* that is not found does not make the font unavailable. The font is created in the generic sans serif family instead, as the GDI+ C++ class does, and [**LastResult**](GdiPlusBase) stays **Ok**. To find out whether a name exists, create a [**FontFamily**](FontFamily) from it and read [**IsAvailable**](FontFamily#isavailable). ```tb check_build check_run @@ -57,6 +59,7 @@ Dim g As GdiPlus.Font = Font("No such family", 20, FontStyleItalic, UnitPixel) Debug.Print g.IsAvailable ' True Debug.Print g.Family.FamilyName = GenericSansSerif().FamilyName ' True ``` + ### From a family @@ -65,6 +68,7 @@ Syntax: *font* **= Font**( *family*, *emSize* [, *style* [, *unit* [, *fontColle *family* : *required* A [**FontFamily**](FontFamily). + The other parameters are as for a family name. A *family* that is not available gives a font that is not available: [**IsAvailable**](#isavailable) returns **False** and **LastResult** is **InvalidParameter**. So does an *emSize* of 0 or less. ```tb check_build check_run @@ -80,6 +84,7 @@ Dim bad As GdiPlus.Font = Font(FontFamily("No such family"), 12!) Debug.Print bad.IsAvailable ' False Debug.Print bad.LastResult ' 2 ``` + ### From a device context @@ -156,7 +161,9 @@ Returns the [**GpUnit**](Enumerations/GpUnit) value of [**Size**](#size). ## Metrics + The metrics of a font come from its family's design-unit values ([**EmHeight**](FontFamily#emheight), [**CellAscent**](FontFamily#cellascent), [**CellDescent**](FontFamily#celldescent) and [**LineSpacing**](FontFamily#linespacing) of the family, for the font's style), scaled by the font's size. [**Ascent**](#ascent), [**Descent**](#descent) and [**LineSpacing**](#linespacing) are in the font's [**Unit**](#unit); [**GetHeight**](#getheight) is in pixels or in the unit of a **Graphics** object. They return 0 when the family's metrics cannot be read. + ### Ascent {: .no_toc } @@ -192,6 +199,7 @@ Syntax: *height* **=** *font*.**GetHeight**( *dpi* ) *dpi* : *required* A **Single** with the number of pixels per inch of the device. + Returns a **Single** with the line spacing of the font. With *dpi*, the result is in pixels for a device of that resolution. A font in pixels has the same height at every resolution, and a font in points has a height proportional to the resolution, equal to [**LineSpacing**](#linespacing) at 72 pixels per inch. ```tb check_build check_run @@ -206,6 +214,7 @@ Debug.Print Abs(pt.GetHeight(72!) - pt.LineSpacing) < 0.01 ' True Debug.Print Abs(pt.GetHeight(144!) - 2 * pt.LineSpacing) < 0.01 ' True Debug.Print px.Ascent > 0 And px.Descent > 0 ' True ``` + ## Interoperating with GDI @@ -226,6 +235,7 @@ Syntax: *status* **=** *font*.**GetLogFontW**( *graphics*, *logfont* ) *logfont* : *required* A **LOGFONTA** or **LOGFONTW** structure that receives the description. + Returns a **LOGFONTA** or **LOGFONTW** structure (the package's `GDIP_LOGFONTA` and `GDIP_LOGFONTW` types) that describes the font, in a form that the GDI function **CreateFontIndirect** accepts. The second form of each fills a structure that the caller owns and returns a [**GpStatus**](Enumerations/GpStatus). The height is negative: a pixel font of 20 pixels gives -20. ```tb check_build check_run @@ -238,6 +248,7 @@ Dim lf As Any = f.GetLogFontW(g) Debug.Print lf.LFHeight ' -20 Debug.Print lf.LFWeight ' 700 ``` + ## See Also diff --git a/docs/Reference/Contributed/GdiPlus/FontCollection.md b/docs/Reference/Contributed/GdiPlus/FontCollection.md index 6964474c..31d04d4a 100644 --- a/docs/Reference/Contributed/GdiPlus/FontCollection.md +++ b/docs/Reference/Contributed/GdiPlus/FontCollection.md @@ -12,6 +12,7 @@ The base class of the two classes that hold a set of font families: [**Installed A **FontCollection** is not created on its own. It gives its two derived classes the members that list the families in the collection. A variable declared **As FontCollection** can hold either of them, which is how the *fontCollection* parameter of [**FontFamily**](FontFamily) and of [**Font**](Font) accepts both. The class mirrors the GDI+ [FontCollection](https://learn.microsoft.com/en-us/windows/win32/api/gdiplusheaders/nl-gdiplusheaders-fontcollection) class. + > [!WARNING] 0.9.45.0, BETA 1005 > Neither derived class can be created, so none of the members on this page can be used. See the warning on [**InstalledFontCollection**](InstalledFontCollection#creating) and on [**PrivateFontCollection**](PrivateFontCollection#creating). The rest of this page describes the members as the source defines them. diff --git a/docs/Reference/Contributed/GdiPlus/FontFamily.md b/docs/Reference/Contributed/GdiPlus/FontFamily.md index ccb036ab..9a76512b 100644 --- a/docs/Reference/Contributed/GdiPlus/FontFamily.md +++ b/docs/Reference/Contributed/GdiPlus/FontFamily.md @@ -27,6 +27,7 @@ Syntax: *family* **= New FontFamily**( *name* [, *fontCollection*] ) *fontCollection* : *optional* A [**FontCollection**](FontCollection) in which to look for the family. When it is omitted, the installed fonts are searched. + Creating a family does not fail when the name is unknown. The object is created, [**IsAvailable**](#isavailable) returns **False**, and **LastResult** is **FontFamilyNotFound**. Every other member then fails with **InvalidParameter** and returns an empty value. ```tb check_build check_run @@ -38,6 +39,7 @@ Debug.Print missing.LastResult ' 14 Debug.Print missing.IsBoldAvailable ' False Debug.Print missing.LastResult ' 2 ``` + ### Clone @@ -53,6 +55,7 @@ Syntax: *family* **= GenericSerif**( ) Syntax: *family* **= GenericMonospace**( ) + Each function returns a family that exists on every system, which makes it the safe choice for a font that must not fail. The families are a sans serif face, a serif face and a fixed-pitch face. Windows decides which installed font stands for each. The object is created on the first call and every later call returns the same object. ```tb check_build check_run @@ -64,6 +67,7 @@ Debug.Print a Is GenericSansSerif() ' True Debug.Print GenericSerif() Is GenericSansSerif() ' False Debug.Print GenericMonospace().IsAvailable ' True ``` + ## Name and availability @@ -77,6 +81,7 @@ Syntax: *name* **=** *family*.**FamilyName** [ ( *language* ) ] Returns a **String** with the name of the family. The result is an empty string when the family is not available. + > [!WARNING] 0.9.45.0 > **FamilyName** returns the name followed by a NUL character (`vbNullChar`). The name read back therefore does not equal the name that was given: `FontFamily("Arial").FamilyName = "Arial"` is **False**. Remove the character before comparing or displaying the name, for example with `Replace(name, vbNullChar, "")`. Passing the name, with its NUL, to **FontFamily**( ) or to **Font**( ) still finds the family. > diff --git a/docs/Reference/Contributed/GdiPlus/GdiPlusAbort.md b/docs/Reference/Contributed/GdiPlus/GdiPlusAbort.md index f81a65a0..97fb1d6b 100644 --- a/docs/Reference/Contributed/GdiPlus/GdiPlusAbort.md +++ b/docs/Reference/Contributed/GdiPlus/GdiPlusAbort.md @@ -30,6 +30,7 @@ Syntax: *object*.**Abort**( ) As **Long** GDI+ calls it from time to time during a long operation on the image. Returning a value other than 0 asks GDI+ to stop the operation; returning 0 lets it go on. + > [!WARNING] 0.9.45.0 > Do not use this interface. GDI+ 1.0 and 1.1 do not implement the aborts, and an image that has been given an implementation with **SetAbort** causes an access violation in GDI+ the next time it is drawn. diff --git a/docs/Reference/Contributed/GdiPlus/GdiPlusBase.md b/docs/Reference/Contributed/GdiPlus/GdiPlusBase.md index 3b239bfa..224a28da 100644 --- a/docs/Reference/Contributed/GdiPlus/GdiPlusBase.md +++ b/docs/Reference/Contributed/GdiPlus/GdiPlusBase.md @@ -20,6 +20,7 @@ A **GdiPlusBase** cannot be created. Create one of the classes that inherit from ## Last result + ### LastResult Syntax: *object*.**LastResult** As **GpStatus** @@ -55,6 +56,7 @@ Debug.Print bmp.LastResult ' 2 -- a good c Debug.Print bmp.GetLastResult() ' 2 Debug.Print bmp.LastResult ' 0 ``` + ## Memory @@ -65,6 +67,7 @@ Syntax: *class*.**Free**( *pointer* As **LongPtr** ) Static members. **Alloc** reserves a block of *size* bytes with the allocator of GDI+ and returns its address, or 0. **Free** releases a block that **Alloc** returned. They are for the buffers that GDI+ itself hands over to or takes from a program. + > [!NOTE] 0.9.45.0 > Call them through a class that inherits from **GdiPlusBase**, as in `GdiPlusUser.Alloc(64)`. The name `GdiPlusBase.Alloc` is not recognised. @@ -77,6 +80,7 @@ GdiPlusUser.Free block ## Error handling + By default a failure is only recorded in **LastResult**. Three variables of the package, shared by all objects, choose another behaviour. ### GdipErrorHandlingStrategy @@ -135,6 +139,7 @@ Dim c As Color = bmp.Pixel(100, 100) ' outside the bitmap Debug.Print LastFailure ' Bitmap 2 GdipErrorHandlingStrategy = IgnoreErrors ``` + ## Protected members diff --git a/docs/Reference/Contributed/GdiPlus/GdiPlusUser.md b/docs/Reference/Contributed/GdiPlus/GdiPlusUser.md index 4a099d54..826991be 100644 --- a/docs/Reference/Contributed/GdiPlus/GdiPlusUser.md +++ b/docs/Reference/Contributed/GdiPlus/GdiPlusUser.md @@ -21,6 +21,7 @@ Dim bmp As Bitmap = Bitmap(64!, 64!) Debug.Print bmp.LastResult ' 0 ``` + > [!WARNING] 0.9.45.0 > Two whole numbers without a format do not create a bitmap of that size. `Bitmap(64, 64)` binds to the form that takes a GDI bitmap handle and a palette handle, treats 64 and 64 as handles, and returns an unusable object with **LastResult** 7 (**Win32Error**), without any error being raised. Pass **Single** values: `Bitmap(64!, 64!)`, or give a format, as in `Bitmap(64, 64, PixelFormat32bppARGB)`. [**Bitmap**](Bitmap#creating) has the details. @@ -42,9 +43,11 @@ The module function **GdiPlusUser** and the `New` form take the same arguments. *output* : *required* A [**GdiplusStartupOutput**](Types/GdiplusStartupOutput) that receives the hook and unhook functions of GDI+. Needed when *input* has **SuppressBackgroundThread** set. + > [!NOTE] 0.9.45.0 > Every instance calls GDI+ to start with its own arguments, not only the first. A later instance given an invalid *input* reports the failure in its **LastResult**, although GDI+ is already running. + > [!NOTE] 0.9.45.0 > Releasing the last instance does not shut GDI+ down: objects that exist keep working, and new ones can still be made. GDI+ stays started until the program ends. @@ -101,6 +104,7 @@ Syntax: *user*.**TestControlNoICM**( *noICM* As **Boolean** ) As **GpStatus** Meant to switch two debugging switches of GDI+: bilinear scaling forced for every scaled image, and the colour management turned off. + > [!WARNING] 0.9.45.0 > Both methods call themselves without end. Calling either on a **GdiPlusUser** ends the program with a stack overflow. Call the module functions of the same names, [**TestControlForceBilinear**](Helpers#testcontrolforcebilinear) and [**TestControlNoICM**](Helpers#testcontrolnoicm), which do what the methods were written to do. diff --git a/docs/Reference/Contributed/GdiPlus/Graphics.md b/docs/Reference/Contributed/GdiPlus/Graphics.md index ac353aec..79721853 100644 --- a/docs/Reference/Contributed/GdiPlus/Graphics.md +++ b/docs/Reference/Contributed/GdiPlus/Graphics.md @@ -36,6 +36,7 @@ A **Graphics** object is created with a package function of the same name as the A **Graphics** object does not own its target. The device context or window must stay valid for as long as the object exists. A **Graphics** object created for an image keeps a reference to the image, so the image is not released before the **Graphics** object. + > [!NOTE] 0.9.45.0 > The package needs a [**GdiPlusUser**](GdiPlusUser) object to be alive for as long as any GDI+ object exists. A program creates one before the first **Graphics** object and keeps it. @@ -55,6 +56,7 @@ Creates a **Graphics** object that draws on an image. Drawing on a bitmap changes the pixels of the bitmap. The three forms are the same. + > [!NOTE] 0.9.45.0 > The function and the constructor with a value of **Nothing** do not report an error. They return an object whose **LastResult** is **Ok**, and every drawing call on it then fails with **InvalidParameter**. @@ -198,6 +200,7 @@ Sub Form_Resize() End Sub ``` + > [!WARNING] 0.9.45.0 > A **GdiPlusUser** declared with **Dim ... As New** is not created until the variable is first used, so the GDI+ objects that are created before that fail with **GdiplusNotInitialized** (18). Declare the variable and set it with `Set mGdiPlus = GdiPlusUser()` in the **Load** event, as above. @@ -254,6 +257,7 @@ Fills the whole drawing surface with a colour. The colour replaces what was ther *rgb* : *required* A **Long** colour. + > [!WARNING] 0.9.45.0 > The *rgb* form reads its **Long** as `&H00RRGGBB`, not as a VB colour: `Clear(vbRed)` and `Clear(RGB(255, 0, 0))` fill with blue. It also forces the alpha value to 255, so a [**CColor**](Enumerations/CColor) member passed to it loses its alpha. Pass a **Color**, as in `Clear(Color(255, 0, 0))`. @@ -283,6 +287,7 @@ The methods that draw outlines take a [**Pen**](Pen) first. The outline is centr Almost every method exists in two sets of overloads. The floating-point set takes **Single**, [**GpPointF**](Types/GpPointF), [**GpSizeF**](Types/GpSizeF) and [**GpRectF**](Types/GpRectF), and arrays of them. The integer set takes **Long**, [**GpPoint**](Types/GpPoint), [**GpSize**](Types/GpSize) and [**GpRect**](Types/GpRect), and arrays of them. The two never mix in one call. When a method has overloads that take plain coordinates, the integer one has the suffix `I`: **DrawLineI**. The overloads with a point, a size or a rectangle have the same name in both sets. + > [!NOTE] 0.9.45.0 > A whole number written in the source is a **Long**, but the call `DrawLine(pen, 0, 5, 15, 5)` takes the floating-point overload: **Long** converts to **Single**. Call the `I` method to use the integer set with plain coordinates. @@ -653,8 +658,9 @@ Draws a string. The text methods have no integer overloads. + > [!NOTE] 0.9.45.0 -> **Font** is also the name of the standard font type of the **stdole** library. A program that references both writes the package's class as `GdiPlus.Font`, as in `Dim f As GdiPlus.Font = GdiPlus.Font("Segoe UI", 12)`. Without the prefix the function does not return a usable font. +> **Font** is also the name of the standard font type of the **stdole** library. A variable declared `As Font` has that type, and the package's font cannot be assigned to it: the assignment raises error -2147467262, *No such interface supported*. Declare the variable with the prefix, as in `Dim f As GdiPlus.Font = GdiPlus.Font("Segoe UI", 12)`. The function `Font(...)` itself needs no prefix. ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() @@ -712,6 +718,7 @@ Draws characters, or glyphs of the font, at positions that the program gives. Th *matrix* : *required* A [**Matrix**](Matrix) that transforms the glyph positions, or **Nothing**. + > [!NOTE] 0.9.45.0 > A call that passes a **SolidBrush** variable, or the number 0 as *flags*, can match both the *str* and the *glyphs* overloads, and the compiler then reports that it cannot tell the overloads apart. Declare the brush variable as `Brush`, and pass the flags as a variable of the type **GpDriverStringOptions**. @@ -965,8 +972,9 @@ Draws an image, or a part of it. The last form needs GDI+ 1.1, which Windows 7 and later have. Of the overloads with plain coordinates, only the ones with a position, with a position and a size, and with a position and a source part have the integer method **DrawImageI**. + > [!WARNING] 0.9.45.0 -> The calls `DrawImage(image, rect)`, with a **GpRectF** or a **GpRect**, and `DrawImage(image, destPoints, srcX, srcY, srcWidth, srcHeight, srcUnit)` with an array of **GpPointF** do not return. Use the overload with *x*, *y*, *width* and *height* (or **DrawImageI**), the one with *destRect* and *srcRect*, or the integer form of the second call. +> The calls `DrawImage(image, rect)`, with a **GpRectF** or a **GpRect**, end a 32-bit program with an access violation. `DrawImage(image, destPoints, srcX, srcY, srcWidth, srcHeight, srcUnit)` with an array of **GpPointF** does the same in a 32-bit and in a 64-bit program. Use the overload with *x*, *y*, *width* and *height* (or **DrawImageI**), the one with *destRect* and *srcRect*, or the integer form of the second call. ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() @@ -1324,6 +1332,7 @@ Syntax: *region* **=** *graphics*.**Clip** Returns the clipping area as a [**Region**](Region). + > [!NOTE] 0.9.45.0 > Reading **Clip** always fails: it returns **Nothing** and **LastResult** becomes **InvalidParameter**. To read the clipping area, read **ClipBounds**, or keep the [**Region**](Region) that the program passed to **SetClip**. @@ -1424,8 +1433,9 @@ Returns **True** when a point, or any part of a rectangle, is inside the visible *rect* : *required* A [**GpRectF**](Types/GpRectF) or a [**GpRect**](Types/GpRect). + > [!WARNING] 0.9.45.0 -> The call with a **GpPointF**, `IsVisible(point)`, does not return. Use the call with *x* and *y*, or the **GpPoint** form. +> The call with a **GpPointF**, `IsVisible(point)`, ends a 32-bit program with an access violation. In a 64-bit program it works. Use the call with *x* and *y*, or the **GpPoint** form. ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() @@ -1636,6 +1646,7 @@ Syntax: *status* **=** *graphics*.**GetTransform**( *matrix* ) *matrix* : *required* A [**Matrix**](Matrix). + > [!NOTE] 0.9.45.0 > Reading **Transform** always fails: it returns **Nothing** and **LastResult** becomes **InvalidParameter**. Create a **Matrix** with `Matrix()` and read the transformation with **GetTransform**. @@ -1725,6 +1736,7 @@ Shears the coordinates: each x moves by *shearX* times y, and each y by *shearY* *order* : *optional* A [**GpMatrixOrder**](Enumerations/GpMatrixOrder). The default is **MatrixOrderPrepend**. + > [!WARNING] 0.9.45.0 > The method always returns **InvalidParameter** and leaves the transformation as it was. To shear, pass a [**Matrix**](Matrix) with the factors in its off-diagonal elements, `Matrix(1, shearY, shearX, 1, 0, 0)`, to **MultiplyTransform**. diff --git a/docs/Reference/Contributed/GdiPlus/GraphicsPath.md b/docs/Reference/Contributed/GdiPlus/GraphicsPath.md index 759069e1..1dc02978 100644 --- a/docs/Reference/Contributed/GdiPlus/GraphicsPath.md +++ b/docs/Reference/Contributed/GdiPlus/GraphicsPath.md @@ -45,6 +45,7 @@ Syntax: *path* **= GraphicsPath(** *points*(), *types*() [, *fillMode* ] **)** These forms create a path from existing point data: a [**PathData**](PathData), an array of [**TypedPoint**](Types/TypedPoint), or an array of **GpPointF** or **GpPoint** together with an array of [**PathPointType**](Types/PathPointType) of the same length. + > [!WARNING] 0.9.45.0 > The forms that take point data do not create the path they are given. The call reports no error, and the path is empty: **PointCount** is 0, and a call of **GetPoints** on it raises an error. With an array of **GpPoint** the call records **OutOfMemory** in a 64-bit program and stops a 32-bit program with an access violation. Build the path with the **Add** methods instead. @@ -55,6 +56,7 @@ Syntax: *path*.**Clone** **As GraphicsPath** Returns a new path with the same points, point types and fill mode. A change to one path does not change the other. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() Dim p As GraphicsPath = GraphicsPath() @@ -63,6 +65,7 @@ Dim c As GraphicsPath = p.Clone p.AddRectangle 0, 0, 5, 5 Debug.Print p.PointCount & " " & c.PointCount ' 8 4 ``` + ## Properties @@ -75,6 +78,7 @@ A [**GpFillmode**](Enumerations/GpFillmode) that decides which areas of a path t With **FillModeAlternate**, a point is inside if a ray from it to infinity crosses an odd number of the path's lines. With **FillModeWinding**, a point is inside if the lines crossed do not cancel out, counting a line one way as +1 and the other way as -1. Two rectangles added to a path run in the same direction. The inner one is a hole with the alternate mode and is filled with the winding mode: + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() Dim p As GraphicsPath = GraphicsPath() @@ -84,6 +88,7 @@ Debug.Print p.IsVisible(5, 5) ' False p.FillMode = FillModeWinding Debug.Print p.IsVisible(5, 5) ' True ``` + ### PointCount {: .no_toc } @@ -99,6 +104,7 @@ Syntax: *path*.**LastPoint** **As GpPointF** The last point of the path: the end of the last figure. An empty path has no last point and the property records an error in **LastResult**. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() Dim p As GraphicsPath = GraphicsPath() @@ -106,6 +112,7 @@ p.AddLine 0, 0, 10, 20 Dim pt As GpPointF = p.LastPoint Debug.Print pt.X & " " & pt.Y ' 10 20 ``` + ### PathData {: .no_toc } @@ -114,6 +121,7 @@ Syntax: *path*.**PathData** **As PathData** Returns the points and point types of the path as a [**PathData**](PathData). + > [!NOTE] 0.9.45.0 > The property returns **Nothing** and records **InvalidParameter** in **LastResult**, whatever the path holds. Read the points with [**GetPoints**](#getpoints) and the point types with [**GetTypes**](#gettypes). @@ -121,6 +129,7 @@ Returns the points and point types of the path as a [**PathData**](PathData). This example makes a path of two figures, closes the second, and lists the types the points get. A type is a number: 0 begins a figure, 1 is a line, 3 is part of a Bézier curve, and the letters show the close flag **c** and the marker flag **m**. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() Dim p As GraphicsPath = GraphicsPath() @@ -141,6 +150,7 @@ For i = 1 To UBound(ty) Next Debug.Print s ' 0 1 0 1 1cm ``` + ### Reset {: .no_toc } @@ -222,6 +232,7 @@ Syntax: *path*.**AddLines(** *points*() **)** **As GpStatus** Adds a series of connected lines through the points. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() Dim p As GraphicsPath = GraphicsPath() @@ -232,6 +243,7 @@ pts(1) = GpPointF(20, 0): pts(2) = GpPointF(30, 10): pts(3) = GpPointF(20, 20) p.AddLines pts Debug.Print p.PointCount ' 5 ``` + ### AddArc {: .no_toc } @@ -277,6 +289,7 @@ Syntax: *path*.**AddBeziers(** *points*() **)** **As GpStatus** Adds a series of connected Bézier curves. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() Dim p As GraphicsPath = GraphicsPath() @@ -290,6 +303,7 @@ Debug.Print q.AddBeziers(pts) & " " & q.PointCount ' 0 7 Dim r(1 To 3) As GpPointF Debug.Print q.AddBeziers(r) ' 2 ``` + ### AddCurve {: .no_toc } @@ -319,6 +333,7 @@ Syntax: *path*.**AddClosedCurve(** *points*() [, *tension* ] **)** **As GpStatus Adds a closed cardinal spline through the points. It is a figure of its own. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() Dim pts(1 To 3) As GpPointF @@ -330,6 +345,7 @@ Dim c As GraphicsPath = GraphicsPath() c.AddClosedCurve pts Debug.Print c.PointCount ' 10 ``` + ## Adding shapes @@ -386,6 +402,7 @@ Syntax: *path*.**AddPolygon(** *points*() **)** **As GpStatus** Adds a closed polygon, as a figure of its own. The last point need not repeat the first. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() Dim p As GraphicsPath = GraphicsPath() @@ -396,6 +413,7 @@ tri(1) = GpPointF(40, 0): tri(2) = GpPointF(50, 0): tri(3) = GpPointF(45, 8) p.AddPolygon tri Debug.Print p.PointCount ' 20 ``` + ### AddPath {: .no_toc } @@ -459,6 +477,7 @@ Syntax: *path*.**Transform(** *matrix* **)** **As GpStatus** Multiplies each point by the matrix. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() Dim p As GraphicsPath = GraphicsPath() @@ -468,6 +487,7 @@ p.Transform m Dim pt As GpPointF = p.LastPoint Debug.Print pt.X & " " & pt.Y ' 14 26 ``` + ### Flatten {: .no_toc } @@ -482,6 +502,7 @@ Syntax: *path*.**Flatten(** [ *matrix* ], [ *flatness* ] **)** **As GpStatus** Replaces every curve with a series of straight lines. The curve information is lost. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() Dim p As GraphicsPath = GraphicsPath() @@ -504,6 +525,7 @@ For i = 1 To UBound(ty) Next Debug.Print (p.PointCount > 13) & " " & n ' True 0 ``` + ### Widen {: .no_toc } @@ -518,6 +540,7 @@ Syntax: *path*.**Widen(** *pen*, [ *matrix* ], [ *flatness* ] **)** **As GpStatu Replaces the path with the outline of the area that the pen paints when it draws the path. The outline is flattened and its fill mode is **FillModeWinding**. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() Dim p As GraphicsPath = GraphicsPath() @@ -529,6 +552,7 @@ Dim b As GpRectF p.GetWorldBounds b, Nothing, Nothing Debug.Print b.Y & " " & b.Height ' -1 2 ``` + ### Outline {: .no_toc } @@ -540,6 +564,7 @@ Syntax: *path*.**Outline(** [ *matrix* ], [ *flatness* ] **)** **As GpStatus** Flattens the path and replaces it with the outline of the area that **FillModeWinding** fills. Where figures overlap, only the outer boundary remains. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() Dim p As GraphicsPath = GraphicsPath() @@ -564,6 +589,7 @@ For i = 1 To UBound(ty) Next Debug.Print n ' 1 ``` + ### Warp {: .no_toc } @@ -589,6 +615,7 @@ Syntax: *path*.**Warp(** *destPoints*(), *srcX*, *srcY*, *srcWidth*, *srcHeight* Warps the path so that the source rectangle fills the quadrilateral given by the destination points, and flattens it. The curve information is lost. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() Dim p As GraphicsPath = GraphicsPath() @@ -601,6 +628,7 @@ Dim b As GpRectF p.GetWorldBounds b, Nothing, Nothing Debug.Print Round(b.Width, 2) & " " & Round(b.Height, 2) ' 20 10 ``` + ## Reading a path @@ -612,6 +640,7 @@ Syntax: *path*.**GetPoints(** *points*() **)** **As GpStatus** *points* : *required* A dynamic array of **GpPointF** or **GpPoint**. The call sizes it to the number of points, with indexes 1 to **PointCount**, and fills it. + > [!NOTE] 0.9.45.0 > On an empty path the call raises run-time error -2147467259, *Unspecified error*. Check **PointCount** first. @@ -623,9 +652,11 @@ Syntax: *path*.**GetTypes(** *types*() **)** **As GpStatus** *types* : *required* A dynamic array of [**PathPointType**](Types/PathPointType). The call sizes it to the number of points, with indexes 1 to **PointCount**, and fills it. + > [!NOTE] 0.9.45.0 > On an empty path the call raises run-time error -2147467259, *Unspecified error*. Check **PointCount** first. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() Dim p As GraphicsPath = GraphicsPath() @@ -638,6 +669,7 @@ For i = 1 To UBound(pts) Next Debug.Print s ' (1,2) (11,2) (11,22) (1,22) ``` + ### GetWorldBounds {: .no_toc } @@ -655,6 +687,7 @@ Syntax: *path*.**GetWorldBounds(** *bounds*, *matrix*, *pen* **)** **As GpStatus Finds a rectangle that holds the path. The rectangle need not be the smallest one: for a curve it can be larger than the curve, and with a pen it can be much larger than the line. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() Dim p As GraphicsPath = GraphicsPath() @@ -663,6 +696,7 @@ Dim b As GpRectF p.GetWorldBounds b, Nothing, Nothing Debug.Print b.X & " " & b.Y & " " & b.Width & " " & b.Height ' 1 2 10 20 ``` + ## Hit testing @@ -700,6 +734,7 @@ Syntax: *path*.**IsOutlineVisibleI(** *x*, *y*, *pen*, [ *g* ] **)** **As Boolea The other arguments are as for **IsVisible**. Returns **True** when the point touches the outline of the path, drawn with the pen. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() Dim p As GraphicsPath = GraphicsPath() @@ -708,9 +743,11 @@ Dim pn As Pen = Pen(Color(255, 255, 0, 0), 2) Debug.Print p.IsVisible(5, 5) & " " & p.IsVisible(15, 15) ' True False Debug.Print p.IsOutlineVisible(0, 5, pn) & " " & p.IsOutlineVisible(5, 5, pn) ' True False ``` + ## Example + This example fills a heart-shaped path made of two arcs and a polygon, then reads two pixels back to show which areas are filled and which are not. ```tb check_build check_run @@ -732,6 +769,7 @@ g.FillPath sb, p Debug.Print Hex$(bmp.Pixel(20, 20).G) ' 0 Debug.Print Hex$(bmp.Pixel(2, 2).G) ' FF ``` + ## See Also diff --git a/docs/Reference/Contributed/GdiPlus/HatchBrush.md b/docs/Reference/Contributed/GdiPlus/HatchBrush.md index 1b887aee..49c01a41 100644 --- a/docs/Reference/Contributed/GdiPlus/HatchBrush.md +++ b/docs/Reference/Contributed/GdiPlus/HatchBrush.md @@ -29,12 +29,15 @@ Syntax: *brush* **= New HatchBrush**( *hatchStyle*, *foreRgb*, *backRgb* ) *foreRgb*, *backRgb* : *required* **Long** values for the same colours, as `&H00RRGGBB`. The alpha values are always 255. + > [!WARNING] 0.9.45.0 -> The function **HatchBrush**( ), the form without **New**, calls itself and never returns. Use **New HatchBrush**. +> The function **HatchBrush**( ), the form without **New**, calls itself until the program stops with a stack overflow. Use **New HatchBrush**. + > [!WARNING] 0.9.45.0 > The *foreRgb* and *backRgb* form reads each **Long** as `&H00RRGGBB`, not as a VB colour, and drops the alpha: `vbRed` is blue. [**IBrush**](IBrush#colours) lists what else is not translated. Use the **Color** form. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() @@ -54,6 +57,7 @@ Debug.Print row ' Output: ' FBBB ``` + ### Clone diff --git a/docs/Reference/Contributed/GdiPlus/Helpers.md b/docs/Reference/Contributed/GdiPlus/Helpers.md index 2adfb962..5aaf2b9b 100644 --- a/docs/Reference/Contributed/GdiPlus/Helpers.md +++ b/docs/Reference/Contributed/GdiPlus/Helpers.md @@ -97,6 +97,7 @@ Syntax: **IsCanonicalPixelFormat**( *format* As **PixelFormat** ) As **Boolean** **True** when the format is one of the canonical formats of GDI+: 32 or 64 bits with an alpha channel. + > [!WARNING] 0.9.45.0 > The result is **False** for **PixelFormat32bppPARGB** and **PixelFormat64bppPARGB**, which are canonical in GDI+, because the values of those two members of the enumeration lack the canonical flag. It is **True** for **PixelFormat32bppARGB** and **PixelFormat64bppARGB**. @@ -243,6 +244,7 @@ Syntax: **GdipCreateStreamOnFile**( *filename* As **LongPtr**, *access* As **Lon The function of the GDI+ flat API of that name, declared by the package. It is meant to open the file whose name *filename* points to (the address of a string, as `StrPtr(name)` returns it) as a COM **IStream** and to store it in *stream*, for the stream form of [**GetMetafileHeader**](#getmetafileheader). + > [!WARNING] 0.9.45.0 > The function returns 6 (**NotImplemented**) and stores no stream, whatever the value of *access*. Make the stream with the Windows function `SHCreateStreamOnFileEx` instead, as the sample above does. @@ -405,6 +407,7 @@ Debug.Print GetNative(bmp).Native <> 0 ' True An image file can hold properties: the camera model of a photograph, the time it was taken, the delay of a GIF frame. Each has a numeric ID, and the constants below name the common ones. An ID is the *propId* argument of [**Image.PropertyItem**](Image#propertyitem) and the **Id** field of a [**GpPropertyItem**](Types/GpPropertyItem). The tables give each constant by its full name and its value. The names are those of the GDI+ C++ header, spelling included (`PropertyTagTransferFuncition`). The type of a property's value is the one that the TIFF, EXIF or GPS standard gives the tag; the constants do not say what it is. The thumbnail tags (`PropertyTagThumbnail...`) describe the small picture that a file may hold beside the main one. + > [!WARNING] 0.9.45.0 > Each constant is an **Integer**, so the 61 tags from `&H8000` up, such as `PropertyTagCopyright` (`&H8298`), are negative numbers. Passed where a **Long** is expected, as the *propId* of **PropertyItem** and **RemovePropertyItem** are, a negative **Integer** becomes `&HFFFF8298`, the ID of another property, and the call finds nothing. Convert the tag first with `tag And &HFFFF&`, which gives `&H8298`, as the example below does. The same holds for comparing a tag with the **Id** of a **GpPropertyItem**. diff --git a/docs/Reference/Contributed/GdiPlus/HueSaturationLightness.md b/docs/Reference/Contributed/GdiPlus/HueSaturationLightness.md index da9a46af..037c074c 100644 --- a/docs/Reference/Contributed/GdiPlus/HueSaturationLightness.md +++ b/docs/Reference/Contributed/GdiPlus/HueSaturationLightness.md @@ -38,6 +38,7 @@ Each form is also available as **New HueSaturationLightness**( ... ) with the sa A constructor that fails creates an unusable object whose **LastResult** tells why. + > [!WARNING] 0.9.45.0 > The form with *params* stops the program with an access violation, in 32-bit and 64-bit programs. The forms without arguments and with the three values work. To use a structure, create the effect without arguments and set [**Parameters**](#parameters). @@ -49,6 +50,7 @@ Syntax: *params* **=** *fx*.**Parameters**( *size* ) Gets or sets the [**HueSaturationLightnessParams**](Types/HueSaturationLightnessParams). + > [!WARNING] 0.9.45.0 > The getter returns zeros and sets **LastResult** to 2 (**InvalidParameter**) unless it is given *size*, the size of the structure: `fx.Parameters(LenB(Of HueSaturationLightnessParams))`. See [**Effect**](Effect#parameters). diff --git a/docs/Reference/Contributed/GdiPlus/IBrush.md b/docs/Reference/Contributed/GdiPlus/IBrush.md index b0b85d09..31d8e679 100644 --- a/docs/Reference/Contributed/GdiPlus/IBrush.md +++ b/docs/Reference/Contributed/GdiPlus/IBrush.md @@ -30,6 +30,7 @@ Each brush class is created with `New` or with the function of the same name, fo Each brush class inherits from **GdiPlusBase**, so it has the **LastResult** property: the last status that GDI+ reported as a failure, or **Ok**. Unless the program sets **GdipErrorHandlingStrategy**, a failure raises no error. + > [!NOTE] 0.9.45.0 > **LastResult** keeps the last failure until **ClearResult** is called. A property that returns an object returns **Nothing** while **LastResult** is not **Ok**, even when the failure was an earlier, unrelated call, and **Clone** does the same. @@ -39,6 +40,7 @@ A brush deletes its GDI+ object when the last reference to the brush is released Every brush that takes colours has two forms: one with **Color** parameters, and one with **Long** parameters, which are the *rgb* forms. The **Color** forms are the ones to use. + > [!WARNING] 0.9.45.0 > A **Long** passed to an *rgb* parameter is read as `&H00RRGGBB`: red in the third byte and blue in the first. It is not a VB colour (a `COLORREF`, `&H00BBGGRR`), so `SolidBrush(vbRed)` and `SolidBrush(RGB(255, 0, 0))` paint blue, and the colour constants of VB and the system colours (`vbButtonFace`, `&H8000000F`) are not translated. The alpha byte of the **Long** is dropped, so the result is always opaque. A member of **CColor** passed there binds to the **Long** form and loses its alpha: `SolidBrush(CColor.Transparent)` fills opaque white. Pass `Color(`*alpha*`, `*red*`, `*green*`, `*blue*`)`, or `Color(CColor.Transparent)`, to get the colour that was meant. @@ -48,7 +50,9 @@ Every brush that takes colours has two forms: one with **Color** parameters, and Syntax: *copy* **=** *brush*.**CloneBrush**( ) + Returns a new brush of the same class with the same settings, as an **IBrush**. Changing the copy does not change the original. The class of the copy is the class of the original: a **Brush** variable that holds a **HatchBrush** gives a **HatchBrush** back, and can be assigned to a **HatchBrush** variable. + Each class has a **Clone** method as well, which returns the copy as the class itself. @@ -62,8 +66,11 @@ Returns the GDI+ handle of the brush, wrapped in a structure. It is for a progra Syntax: *type* **=** *brush*.**Type** + Returns a [**GpBrushType**](Enumerations/GpBrushType) value for the kind of the brush: **BrushTypeSolidColor**, **BrushTypeHatchFill**, **BrushTypeTextureFill**, **BrushTypePathGradient** or **BrushTypeLinearGradient**. The property is read-only. + + > [!WARNING] 0.9.45.0 > Reading **Type** of a [**PathGradientBrush**](PathGradientBrush#type) stops a program that runs in the IDE, at an assertion that expects a linear gradient. A compiled program is not affected, and returns **BrushTypePathGradient**. diff --git a/docs/Reference/Contributed/GdiPlus/ITransformable.md b/docs/Reference/Contributed/GdiPlus/ITransformable.md index 7ab3696f..da0f6de7 100644 --- a/docs/Reference/Contributed/GdiPlus/ITransformable.md +++ b/docs/Reference/Contributed/GdiPlus/ITransformable.md @@ -51,8 +51,9 @@ Dim e() As Single = m.Elements Debug.Print e(1) & " " & e(4) ' 3 3 ``` + > [!NOTE] 0.9.45.0 -> Reading **Transform** does not work on any of the classes that implement the interface. The getter returns **Nothing** and records **InvalidParameter** in **LastResult**. Read the transformation with [**GetTransform**](#gettransform). +> Reading **Transform** does not work on any of the classes that implement the interface: the getter returns **Nothing**. On a **Graphics** and on the brushes it also records **InvalidParameter** in **LastResult**; on a **Matrix** it leaves **LastResult** as it was. Read the transformation with [**GetTransform**](#gettransform). ## GetTransform {: .no_toc } diff --git a/docs/Reference/Contributed/GdiPlus/Image.md b/docs/Reference/Contributed/GdiPlus/Image.md index 1e935d1d..d9b8f418 100644 --- a/docs/Reference/Contributed/GdiPlus/Image.md +++ b/docs/Reference/Contributed/GdiPlus/Image.md @@ -14,8 +14,9 @@ An **Image** holds either raster data or a recorded sequence of drawing commands * TOC {:toc} + > [!NOTE] 0.9.45.0 -> A project that references the **VB** package (every default project does) also has a VB control class named **Image**. A bare `Image` in a declaration means the VB class, so write `GdiPlus.Image` for the class and for the function that creates it, as the samples on this page do. [**Bitmap**](Bitmap) and [**Metafile**](Metafile) have no such clash. +> A project that references the **VB** package (every default project does) also has a VB control class named **Image**. A bare `Image` as a type, as in `Dim img As Image`, means the VB class: `New Image(filename)` does not compile, because the VB class takes no arguments, and assigning a [**Bitmap**](Bitmap) to such a variable raises error -2147467262 (*No such interface supported*) at run time. Write `GdiPlus.Image` for the type, as the samples on this page do. The function **Image**( *filename* ) works without the prefix. [**Bitmap**](Bitmap) and [**Metafile**](Metafile) have no such clash. ```tb check_build hidden Module ImagePageSaver @@ -96,6 +97,7 @@ Syntax: *copy* **=** *img*.**Clone**( ) Returns a new **Image** that holds a copy of the picture. The copy is an **Image**, even when the original is a [**Bitmap**](Bitmap) or a [**Metafile**](Metafile). + > [!NOTE] 0.9.45.0 > [**Bitmap**](Bitmap#clone) and [**Metafile**](Metafile) declare their own **Clone**, and on a **Bitmap** the no-argument form is not available: the call does not compile. To copy a whole **Bitmap**, use [**Bitmap.Clone**](Bitmap#clone) with the full rectangle. @@ -119,10 +121,11 @@ Syntax: *status* **=** *img*.**Save**( *stream*, *clsidEncoder*, *encoderParams* : *required* The class ID of the encoder, which also selects the file format. *encoderParams* -: *required* An array of [**GpEncoderParameter**](Types/GpEncoderParameter). The call expects its lower bound to be 0, and element 0 to be a dummy that is not filled in; the parameters follow from element 1. Any other lower bound returns **InvalidParameter**. +: *required* An array of [**GpEncoderParameter**](Types/GpEncoderParameter). The call expects its lower bound to be 0, and element 0 to be a dummy that is not filled in; the parameters follow from element 1. In a 64-bit program, any other lower bound returns **InvalidParameter**. + > [!WARNING] 0.9.45.0 -> **Save** does not work with an encoder parameter array. The call stores the parameter count in memory that lies before the array, and gives GDI+ a pointer to that place, so GDI+ reads its parameters from memory the array does not own. With `Dim ep(0 To 0) As GpEncoderParameter` the call returns **InvalidParameter** and leaves an empty file. With a longer array it returns **Ok** in 64-bit programs while GDI+ ignores the parameters (a JPEG saved with a quality of 10 has the same size as one saved with 100), and in 32-bit programs it raises an access violation or does not return. **SaveAdd** and **SaveAddImage** build their argument in the same way. [**Bitmap**](Bitmap#saving) shows how to write a PNG without calling **Save**. +> **Save** does not work with an encoder parameter array. The call stores the parameter count in memory that lies before the array, and gives GDI+ a pointer to that place, so GDI+ reads its parameters from memory the array does not own. In 32-bit programs every call with a parameter array raises an access violation and the program stops. In 64-bit programs, with `Dim ep(0 To 0) As GpEncoderParameter` the call returns **InvalidParameter** and leaves an empty file, and with a longer array it returns **Ok** while GDI+ ignores the parameters (a JPEG saved with a quality of 10 has the same size as one saved with 100). **SaveAdd** and **SaveAddImage** build their argument in the same way: after such a **Save** in a 64-bit program they return **Win32Error**, and no frame is added. [**Bitmap**](Bitmap#saving) shows how to write a PNG without calling **Save**. ### SaveAdd @@ -237,6 +240,7 @@ Syntax: *img*.**Palette** **=** *pal* The getter is meant to return the palette as an array of [**ColorPalette**](Types/ColorPalette), and the setter to take one. + > [!WARNING] 0.9.45.0 > Reading **Palette** overflows the stack, in 32-bit and 64-bit programs, and the process stops. The setter calls the GDI+ function that reads a palette, not the one that writes it, and so does not change the picture. To get a palette, use [**ImageAttributes.GetAdjustedPalette**](ImageAttributes#getadjustedpalette) or fill a [**ColorPalette**](Types/ColorPalette) yourself, and give it to [**Bitmap.ConvertFormat**](Bitmap#convertformat). @@ -374,8 +378,9 @@ Syntax: *img*.**PropertyItem** **=** *item* The getter returns the property with the ID *propId* as an array of [**GpPropertyItem**](Types/GpPropertyItem) that holds one element, at index 1. The setter takes one **GpPropertyItem** and sets that property on the picture, adding it when it is not there. Its [**Id**](Types/GpPropertyItem), type, length and the address of its value must be filled in. -> [!WARNING] 0.9.45.0 -> Reading an element of the array that **PropertyItem** returns stops the process with an access violation, because the array is built in a way that the element access cannot use. Only `UBound` and `LBound` of the array work. **GetAllPropertyItems** returns its array in the same way and has the same defect. The setter, **PropertyCount**, **PropertyIdList** and **RemovePropertyItem** work. + +> [!WARNING] BETA 1005 +> A property of an element of the array that **PropertyItem** returns, such as `items(1).TheASCII`, stops the process with an access violation, as it does for an element of any array of a user-defined type. An element can be read as a whole or by a field: `items(1).Id` works, and so does `Dim got As GpPropertyItem = items(1)` followed by `got.TheASCII`. **GetAllPropertyItems** returns its array in the same way, and the same holds for it. The setter, **PropertyCount**, **PropertyIdList** and **RemovePropertyItem** work. ### RemovePropertyItem @@ -419,12 +424,13 @@ Syntax: *params* **=** *img*.**GetEncoderParameterList**( *clsidEncoder* ) Returns the parameters that the encoder with the class ID *clsidEncoder* accepts for this picture, as an array of [**GpEncoderParameter**](Types/GpEncoderParameter). + > [!WARNING] 0.9.45.0 > **GetEncoderParameterList** stops the process with an access violation in 32-bit and 64-bit programs. The call hands GDI+ the address of a temporary variable in place of the buffer, so GDI+ overwrites the stack. ## Items of image data -Some formats store data beside the pixels. **FindFirstItem**, **FindNextItem** and **GetItemData** enumerate and read it, with a [**GpImageItemData**](Types/GpImageItemData) in an array of one element. These members need GDI+ 1.1 and Windows 7 or later, and only the codecs that provide such data return any. The array is built as for **PropertyItem**, so its element cannot be read. +Some formats store data beside the pixels. **FindFirstItem**, **FindNextItem** and **GetItemData** enumerate and read it, with a [**GpImageItemData**](Types/GpImageItemData) in an array of one element. These members need GDI+ 1.1 and Windows 7 or later, and only the codecs that provide such data return any. The array is built as for **PropertyItem**, and the warning under it applies. ### FindFirstItem diff --git a/docs/Reference/Contributed/GdiPlus/ImageAttributes.md b/docs/Reference/Contributed/GdiPlus/ImageAttributes.md index 5bc34bd4..42b97af5 100644 --- a/docs/Reference/Contributed/GdiPlus/ImageAttributes.md +++ b/docs/Reference/Contributed/GdiPlus/ImageAttributes.md @@ -80,6 +80,7 @@ A colour matrix changes the colour of every pixel with a linear transformation: In the `m(i, j)` array of the package, the first index *i* picks the output component and the second index *j* the input component. The indexes 0 to 3 stand for red, green, blue and alpha. The input index 4 is the constant 1, so `m(i, 4)` adds a constant to the component *i*. `m(2, 0) = 1` adds the red of the input to the blue of the output. A variable declared **As ColorMatrix** is all zeros, and a matrix of zeros makes every colour black, so a program starts from the identity matrix, which has 1 in `m(i, i)`. + > [!NOTE] 0.9.45.0 > The C++ matrix has its indexes the other way round, `M[input][output]`, so `M[0][2]` of C++ is `m(2, 0)` here. The package swaps the indexes to match the memory layout. @@ -234,6 +235,7 @@ Syntax: *status* **=** *attrs*.**SetColorKey**( *rgbLow*, *rgbHigh* [ , *type* ] Makes the pixels whose colour lies in the range (every channel at or above *colorLow* and at or below *colorHigh*) transparent. Use the same colour twice to key out one colour. + > [!WARNING] 0.9.45.0 > The **Long** form reads its arguments as `&H00RRGGBB`, not as a VB colour. `SetColorKey RGB(200, 100, 50), RGB(200, 100, 50)` keys out a different colour, as red and blue are exchanged. Use the **Color** form, or write the value as `&HRRGGBB`. diff --git a/docs/Reference/Contributed/GdiPlus/ImageCodec.md b/docs/Reference/Contributed/GdiPlus/ImageCodec.md index e7735d68..75f3d2a9 100644 --- a/docs/Reference/Contributed/GdiPlus/ImageCodec.md +++ b/docs/Reference/Contributed/GdiPlus/ImageCodec.md @@ -24,6 +24,7 @@ Syntax: *codecs* **= GetImageDecoders**( ) Return an array of [**GpImageCodecInfo**](Types/GpImageCodecInfo), one element for each installed encoder or decoder, with the lower bound 1. + > [!WARNING] 0.9.45.0 > Both functions stop the program with an access violation, in 32-bit and 64-bit programs. They give GDI+ the array variable itself as the buffer to fill, so GDI+ overwrites the array descriptor. Use the declared functions below, which work. @@ -48,6 +49,7 @@ Syntax: *status* **= GetImageDecoders**( *count*, *size*, *buffer* ) These are direct declarations of the GDI+ functions, and return a [**GpStatus**](Enumerations/GpStatus). The buffer receives *count* consecutive **GpImageCodecInfo** structures, followed by the strings they point to. A program reads an element by copying `LenB(Of GpImageCodecInfo)` bytes from the buffer into a variable of that type. The buffer must stay alive for as long as the strings are used. + > [!WARNING] 0.9.45.0 > The string properties of [**GpImageCodecInfo**](Types/GpImageCodecInfo) (**FormatDescription**, **MimeType**, **FilenameExtension** and the others) end with a null character, so `info.FormatDescription = "PNG"` is **False**. Remove it with `Replace(text, vbNullChar, "")`, as the example does. @@ -173,6 +175,7 @@ An encoder accepts parameters, such as the quality of a JPEG. Each parameter is | **EncoderSaveFlag** | Whether the save starts or continues a multi-frame file. | | **EncoderColorSpace**, **EncoderImageItems**, **EncoderSaveAsCMYK** | Colour space, extra data items and CMYK saving (GDI+ 1.1). | + > [!WARNING] 0.9.45.0 > **Image.Save** cannot pass encoder parameters to the encoder, so these IDs cannot be used to change how a picture is saved. The call reads the parameters from memory before the array, and either returns an error, ignores them, or stops the program. See the warning at [**Image.Save**](Image#save); [**Bitmap**](Bitmap#saving) shows a way to save a PNG without parameters. diff --git a/docs/Reference/Contributed/GdiPlus/InstalledFontCollection.md b/docs/Reference/Contributed/GdiPlus/InstalledFontCollection.md index 49ce1361..76d15091 100644 --- a/docs/Reference/Contributed/GdiPlus/InstalledFontCollection.md +++ b/docs/Reference/Contributed/GdiPlus/InstalledFontCollection.md @@ -20,6 +20,7 @@ Syntax: *collection* **= InstalledFontCollection**( ) Syntax: *collection* **= New InstalledFontCollection** + > [!WARNING] 0.9.45.0, BETA 1005 > Creating an **InstalledFontCollection** raises run-time error 438, *Object doesn't support this property or method*, and the result is **Nothing**. The statement `Debug.Assert TypeOf (Me) Is InstalledFontCollection` in the class raises it. Even with that statement removed, the constructor does not store the native collection it creates, so the object has no handle and every call on it fails with **InvalidParameter**. The class cannot be used until a version of the package corrects both. diff --git a/docs/Reference/Contributed/GdiPlus/Levels.md b/docs/Reference/Contributed/GdiPlus/Levels.md index c2da94b4..dce11b7d 100644 --- a/docs/Reference/Contributed/GdiPlus/Levels.md +++ b/docs/Reference/Contributed/GdiPlus/Levels.md @@ -38,6 +38,7 @@ Each form is also available as **New Levels**( ... ) with the same arguments. A constructor that fails creates an unusable object whose **LastResult** tells why. + > [!WARNING] 0.9.45.0 > The form with *params* stops the program with an access violation, in 32-bit and 64-bit programs. The forms without arguments and with the three values work. To use a structure, create the effect without arguments and set [**Parameters**](#parameters). @@ -49,9 +50,11 @@ Syntax: *params* **=** *fx*.**Parameters**( *size* ) Gets or sets the [**LevelsParams**](Types/LevelsParams). + > [!NOTE] 0.9.45.0 > The first field of **LevelsParams** is named **highight**, with the letter l missing. Write `params.highight`. + > [!WARNING] 0.9.45.0 > The getter returns zeros and sets **LastResult** to 2 (**InvalidParameter**) unless it is given *size*, the size of the structure: `fx.Parameters(LenB(Of LevelsParams))`. See [**Effect**](Effect#parameters). diff --git a/docs/Reference/Contributed/GdiPlus/LinearGradientBrush.md b/docs/Reference/Contributed/GdiPlus/LinearGradientBrush.md index e99f9c49..fc62edf8 100644 --- a/docs/Reference/Contributed/GdiPlus/LinearGradientBrush.md +++ b/docs/Reference/Contributed/GdiPlus/LinearGradientBrush.md @@ -46,6 +46,7 @@ Each form can also be written with **New**: *brush* **= New LinearGradientBrush* The form with only a rectangle and two colours runs the gradient along the diagonal from the top left corner to the bottom right corner, like **LinearGradientModeForwardDiagonal**. A new brush has the wrap mode **WrapModeTile**. + > [!WARNING] 0.9.45.0 > The forms with *rgb1* and *rgb2* read each **Long** as `&H00RRGGBB`, not as a VB colour, and drop the alpha: `vbRed` is blue. [**IBrush**](IBrush#colours) lists what else is not translated. Use the **Color** forms. @@ -91,6 +92,7 @@ Syntax: *colors* **=** *brush*.**LinearColors** Returns the start colour and the end colour, as an array of **Color**. + > [!NOTE] 0.9.45.0 > The array runs from 0 to 2, and its element 0 is not used: the start colour is element 1 and the end colour element 2. @@ -98,7 +100,9 @@ Returns the start colour and the end colour, as an array of **Color**. Syntax: *colors* **=** *brush*.**LinearRGBs** + Returns the same two colours as an array of **Long**, laid out as the array of **LinearColors**. Each value is a complete `&HAARRGGBB` colour, with its alpha, and not a `&H00RRGGBB` value. + ### Rectangle, RectangleI @@ -154,8 +158,9 @@ Syntax: *brush*.**SetBlend**( *blendFactors*(1), *blendPositions*(1), *count* ) Sets the blend factors, and returns a [**GpStatus**](Enumerations/GpStatus). A blend factor is a **Single** from 0 to 1: the share of the end colour at a position, with the rest from the start colour. A position is a **Single** from 0 at the start of the gradient to 1 at its end. The first position must be 0 and the last 1. The first form takes two arrays of the same length. The second form takes the first element of each array by reference and the number of elements to read. + > [!WARNING] 0.9.45.0 -> The form with two arrays never returns. Use the form with the first elements and the count, as the example does. +> The form with two arrays stops the program with an access violation. Use the form with the first elements and the count, as the example does. ### GetBlend @@ -179,8 +184,9 @@ Syntax: *brush*.**SetInterpolationColors**( *presetColors*(1), *blendPositions*( Sets preset colours, and returns a [**GpStatus**](Enumerations/GpStatus). The gradient takes the colour at each position, and blends between them. The first position must be 0 and the last 1. Like **SetBlend**, the first form takes two arrays and the second form takes the first elements by reference and a count. The preset colours replace the start and the end colour for the drawing; [**LinearColors**](#linearcolors) still returns those two. + > [!WARNING] 0.9.45.0 -> The form with two arrays never returns. Use the form with the first elements and the count. +> The form with two arrays stops the program with an access violation. Use the form with the first elements and the count. ### GetInterpolationColors @@ -194,7 +200,9 @@ Reads the preset colours and their positions. The first form sizes the two array Syntax: *brush*.**SetBlendBellShape**( *focus* [, *scale*] ) + Makes the colour reach the end colour at *focus*, a **Single** from 0 to 1 along the gradient, and fall back to the start colour on both sides, with a smooth bell-shaped curve. *scale*, a **Single** from 0 to 1, is how fast the colour falls off from *focus*; the default is 1. Returns a [**GpStatus**](Enumerations/GpStatus). + ### SetBlendTriangularShape @@ -244,12 +252,15 @@ Syntax: *brush*.**WrapMode** [ **=** *wrapMode* ] Gets or sets what the brush does outside its line: a [**GpWrapMode**](Enumerations/GpWrapMode) value. The default is **WrapModeTile**. + | Value | Outside the gradient | |:------|:---------------------| | **WrapModeTile** | The gradient repeats, from the start colour again. | | **WrapModeTileFlipX**, **WrapModeTileFlipXY** | The gradient repeats, mirrored every second time, so that the colour runs back from the end colour. | | **WrapModeTileFlipY** | Like **WrapModeTile**. | + + > [!NOTE] 0.9.45.0 > **WrapModeClamp** is refused: **LastResult** becomes **InvalidParameter** and the wrap mode is not changed. @@ -294,6 +305,7 @@ Syntax: *brush*.**Transform** [ **=** *matrix* ] Sets the transformation of the brush to a [**Matrix**](Matrix). Reading it should return a copy of the matrix. + > [!NOTE] 0.9.45.0 > Reading **Transform** always fails: it returns **Nothing** and **LastResult** becomes **InvalidParameter**. Use [**GetTransform**](#gettransform). @@ -301,7 +313,9 @@ Sets the transformation of the brush to a [**Matrix**](Matrix). Reading it shoul Syntax: *brush*.**GetTransform**( *matrix* ) + Copies the transformation of the brush into *matrix*, a **Matrix** that the caller created. + ### ResetTransform diff --git a/docs/Reference/Contributed/GdiPlus/Matrix.md b/docs/Reference/Contributed/GdiPlus/Matrix.md index 884dff9b..8564efb7 100644 --- a/docs/Reference/Contributed/GdiPlus/Matrix.md +++ b/docs/Reference/Contributed/GdiPlus/Matrix.md @@ -57,6 +57,7 @@ Syntax: *matrix* **= Matrix(** *rect*, *point* **)** In GDI+ this constructor maps a rectangle to a parallelogram, and the parallelogram is given by three points: the images of the top-left, top-right and bottom-left corners of the rectangle. The package takes one point where GDI+ reads three. + > [!WARNING] 0.9.45.0 > The constructors that take a rectangle and a point give a matrix whose elements depend on whatever memory follows the point. The call does not report an error. Use the six-number constructor, or build the matrix with [**Translate**](#translate), [**Scale**](#scale) and the other methods. @@ -128,6 +129,7 @@ Syntax: *matrix*.**Offset** [ **=** *point* ] The translation elements *dx* and *dy*. **OffsetX** and **OffsetY** are **Single** values. **Offset** is a [**GpPointF**](Types/GpPointF) holding both. + > [!WARNING] 0.9.45.0 > Assigning **OffsetX** sets *dx* and sets *dy* to 0. Assigning **OffsetY** does not change *dy* to the value: it sets *dx* to the value and *dy* to 0. Reading all three properties is correct, and so is assigning **Offset**. To change one offset, assign **Offset**, or call **SetElements**. @@ -213,6 +215,7 @@ Syntax: *matrix*.**Scale(** *sx*, *sy*, [ *order* ] **)** **As GpStatus** Combines a scaling with the matrix. + > [!WARNING] BETA 1005 > A call of **Scale** written as a statement, with its arguments in parentheses and without **Call**, does nothing and raises no error: `m.Scale(2, 3)` leaves the matrix as it was. The same call written `m.Scale 2, 3`, `Call m.Scale(2, 3)` or `status = m.Scale(2, 3)` works. This is the case for a method of any class that is named **Scale**. diff --git a/docs/Reference/Contributed/GdiPlus/Metafile.md b/docs/Reference/Contributed/GdiPlus/Metafile.md index 2f4a848a..d170f1ed 100644 --- a/docs/Reference/Contributed/GdiPlus/Metafile.md +++ b/docs/Reference/Contributed/GdiPlus/Metafile.md @@ -68,6 +68,7 @@ With a *filename* or a *stream* in front, the recording is written there as it i A constructor that fails creates an unusable object whose **LastResult** tells why. + > [!WARNING] 0.9.45.0 > The constructors that take a file name (`Metafile(filename)`, `Metafile(filename, header)`, and the recording forms with a file name) and the forms that record with a *referenceHdc* and a *type* raise run-time error 13 (*Type mismatch*): they pass a **String** to a GDI+ function that expects a pointer. An unhandled error ends the program. `Metafile(referenceHdc)` with nothing else does not record either: it binds to the *hEmf* form, treats the device context as a metafile handle, and returns an unusable object with **LastResult** 1 (**GenericError**). Of the constructors, the *hEmf* and *hWmf* forms work. To load a metafile from a file, use [**Image**](Image#creating): `GdiPlus.Image(filename)` returns an **Image** whose [**Type**](Image#type) is **ImageTypeMetafile**, which can be drawn, and whose header [**GetMetafileHeader**](#metafileheader) reads. @@ -207,6 +208,7 @@ Syntax: *status* **=** *mf*.**ConvertToEmfPlus**( *refGraphics* [ , *conversionF Converts an EMF metafile into an EMF+ metafile. The **Metafile** itself becomes the converted one. It needs GDI+ 1.1 (Windows 7 or later). + > [!WARNING] 0.9.45.0 > The form without a file name or a stream raises run-time error 13 (*Type mismatch*), as the constructors above do. The *filename* form works. diff --git a/docs/Reference/Contributed/GdiPlus/PathData.md b/docs/Reference/Contributed/GdiPlus/PathData.md index b0ce058f..4406d001 100644 --- a/docs/Reference/Contributed/GdiPlus/PathData.md +++ b/docs/Reference/Contributed/GdiPlus/PathData.md @@ -33,12 +33,14 @@ Syntax: *data* **= New PathData** **New PathData** with no arguments creates an object with room for no points. The function form has no version without arguments. Call [**Allocate**](#allocate) to give it room. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() Dim a As PathData = PathData(3) Dim b As PathData = New PathData Debug.Print a.Count & " " & b.Count ' 3 0 ``` + ## Members @@ -69,9 +71,11 @@ Syntax: *data*.**Point(** *index* **)** [ **=** *point* ] Reads or sets the point with the given index, a [**GpPointF**](Types/GpPointF). Reading an index outside 1 to **Count** returns a point at 0, 0. + > [!WARNING] 0.9.45.0 > Reading **Point** stops a 32-bit program with an access violation, whatever the index is, as long as it is in range. A 64-bit program reads correctly. The same is true of **TypedPoint** and of **TypedPoints**, which read points through it. In a program that is built in both widths, read the points with [**Points**](#points), which works in both. + > [!WARNING] 0.9.45.0 > Setting **Point**, **Type** or **TypedPoint** does not check the index. A value outside 1 to **Count** is written to the memory next to the arrays and raises no error. @@ -80,7 +84,9 @@ Reads or sets the point with the given index, a [**GpPointF**](Types/GpPointF). Syntax: *data*.**Type(** *index* **)** [ **=** *type* ] + Reads or sets the [**PathPointType**](Types/PathPointType) of the point with the given index. Reading an index outside 1 to **Count** returns a type of 0. + ### TypedPoint {: .no_toc } @@ -117,10 +123,13 @@ Syntax: *data*.**PointsPtr** **As LongPtr** Syntax: *data*.**TypesPtr** **As LongPtr** + The addresses of the first point and of the first type, for a call of a Windows function. The address is 0 when the object has no room. + The example fills a **PathData** and reads it through the views: + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() Dim d As PathData = PathData(3) @@ -139,9 +148,11 @@ Dim ty() As PathPointType = d.Types Debug.Print LBound(pts) & " " & UBound(pts) ' 1 3 Debug.Print pts(2).X & "," & pts(2).Y & " " & ty(2)._value ' 5,6 1 ``` + A **PathData** is also created from an array of typed points: + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() Dim tp(1 To 3) As TypedPoint @@ -155,6 +166,7 @@ Dim d As PathData = PathData(tp) Dim pts() As GpPointF = d.Points Debug.Print d.Count & " " & pts(3).X & "," & pts(3).Y ' 3 10,10 ``` + ## See Also diff --git a/docs/Reference/Contributed/GdiPlus/PathGradientBrush.md b/docs/Reference/Contributed/GdiPlus/PathGradientBrush.md index a02f4694..be6f9b93 100644 --- a/docs/Reference/Contributed/GdiPlus/PathGradientBrush.md +++ b/docs/Reference/Contributed/GdiPlus/PathGradientBrush.md @@ -33,6 +33,7 @@ Each form can also be written with **New**: *brush* **= New PathGradientBrush**( A new brush has the centre point at the centroid of the path and a black centre colour. Set the colours before the brush is used. + > [!NOTE] 0.9.45.0 > The form with points does not report a failure. A list that GDI+ cannot use, such as a single point, gives a brush whose **LastResult** is **Ok**; every later call on it fails with **InvalidParameter**. @@ -81,6 +82,7 @@ Syntax: *brush*.**CenterRGB** = *rgb* Sets the centre colour from a **Long**, as `&H00RRGGBB`. The property cannot be read. + > [!WARNING] 0.9.45.0 > **CenterRGB** reads its **Long** as `&H00RRGGBB`, not as a VB colour, and the alpha is always 255: `vbRed` gives blue. [**IBrush**](IBrush#colours) lists what else is not translated. Use [**CenterColor**](#centercolor). @@ -88,10 +90,13 @@ Sets the centre colour from a **Long**, as `&H00RRGGBB`. The property cannot be Syntax: *brush*.**SurroundColors** = *colors* + Sets the colours of the boundary. *colors* is an array of **Color**. One colour is used for the whole boundary. A longer array gives each point its own colour, in the order of the points the brush was made from, and the colour between two points changes along the boundary. The array must not have more elements than the path has points. + + > [!WARNING] 0.9.45.0 -> Reading **SurroundColors** never returns. Keep the array that was set. +> Reading **SurroundColors** stops the program with a stack overflow. Keep the array that was set. ### SurroundColorCount @@ -132,7 +137,9 @@ Debug.Print Hex$(bmp.Pixel(0, 0).argb) & " " & Hex$(bmp.Pixel(5, 5).argb) Syntax: *count* **=** *brush*.**PointCount** + Returns the number of points of the path. For a brush made from a path, these are all the points of the path, the control points of its curves included. + ### GraphicsPath @@ -140,6 +147,7 @@ Syntax: *brush*.**GraphicsPath** [ **=** *path* ] Gets or sets the path of the brush as a [**GraphicsPath**](GraphicsPath). + > [!NOTE] 0.9.45.0 > Reading and writing fail: GDI+ answers **NotImplemented**. Reading returns **Nothing**. @@ -165,7 +173,9 @@ Syntax: *brush*.**SetFocusScales**( *xScale*, *yScale* ) Syntax: *brush*.**GetFocusScales**( *xScale*, *yScale* ) + Set or read the focus scales, two **Single** values. The centre colour is not at one point only: it fills a copy of the path that is shrunk about the centre point by *xScale* across and *yScale* down, and the colour changes between that copy and the boundary. The default is 0, one point. Both methods return a [**GpStatus**](Enumerations/GpStatus); **GetFocusScales** puts its results in the two variables. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() @@ -226,8 +236,9 @@ Syntax: *brush*.**SetBlend**( *blendFactors*, *blendPositions* ) Sets the blend factors, with two **Single** arrays of the same length. This is the form that [**LinearGradientBrush**](LinearGradientBrush#setblend) has with two arrays. + > [!WARNING] 0.9.45.0 -> **SetBlend** never returns. Use [**SetBlendBellShape**](#setblendbellshape) or [**SetBlendTriangularShape**](#setblendtriangularshape). +> **SetBlend** stops the program with an access violation. Use [**SetBlendBellShape**](#setblendbellshape) or [**SetBlendTriangularShape**](#setblendtriangularshape). ### InterpolationColorCount @@ -239,7 +250,9 @@ Returns the number of preset colours. A brush that has none returns 0. Syntax: *brush*.**GetInterpolationColors**( *presetColors*, *blendPositions* ) + Sizes a **Color** array and a **Single** array from 1 to **InterpolationColorCount**, fills them, and returns a [**GpStatus**](Enumerations/GpStatus). It returns **WrongState** when the brush has no preset colours. + ### SetInterpolationColors @@ -247,14 +260,17 @@ Syntax: *brush*.**SetInterpolationColors**( *presetColors*, *blendPositions* ) Sets preset colours at positions, with a **Color** array and a **Single** array of the same length. + > [!WARNING] 0.9.45.0 -> **SetInterpolationColors** never returns. +> **SetInterpolationColors** stops the program with an access violation. ### SetBlendBellShape Syntax: *brush*.**SetBlendBellShape**( *focus* [, *scale*] ) + Makes the colour reach the centre colour at *focus*, a **Single** from 0 to 1, and fall off with a smooth bell-shaped curve. *scale*, a **Single** from 0 to 1, is how fast the colour falls off from *focus*. The default is 1. Returns a [**GpStatus**](Enumerations/GpStatus). + ### SetBlendTriangularShape @@ -270,11 +286,13 @@ Syntax: *brush*.**WrapMode** [ **=** *wrapMode* ] Gets or sets what is drawn outside the bounding rectangle of the path: a [**GpWrapMode**](Enumerations/GpWrapMode) value. + | Value | Outside the path | |:------|:-----------------| | **WrapModeClamp** | Nothing is drawn. This is the default of the form with points. | | **WrapModeTile** | The gradient repeats. | | **WrapModeTileFlipX**, **WrapModeTileFlipY**, **WrapModeTileFlipXY** | The gradient repeats, mirrored every second time in x, in y, or in both. | + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() @@ -317,6 +335,7 @@ Syntax: *brush*.**Transform** [ **=** *matrix* ] Sets the transformation of the brush to a [**Matrix**](Matrix). Reading it should return a copy of the matrix. + > [!NOTE] 0.9.45.0 > Reading **Transform** always fails: it returns **Nothing** and **LastResult** becomes **InvalidParameter**. Use [**GetTransform**](#gettransform). @@ -398,6 +417,7 @@ Syntax: *type* **=** *brush*.**Type** Returns **BrushTypePathGradient** ([**GpBrushType**](Enumerations/GpBrushType)). The property is read-only. + > [!WARNING] 0.9.45.0 > In a program that runs in the IDE, reading **Type** stops at an assertion that expects a linear gradient. A compiled program returns the right value. diff --git a/docs/Reference/Contributed/GdiPlus/PathIterator.md b/docs/Reference/Contributed/GdiPlus/PathIterator.md index 7ab58931..5de983cf 100644 --- a/docs/Reference/Contributed/GdiPlus/PathIterator.md +++ b/docs/Reference/Contributed/GdiPlus/PathIterator.md @@ -17,8 +17,9 @@ Each kind of iteration keeps its own position. The calls of **NextSubpath** step Every method exists twice. The *UDT form* has no arguments and returns a user-defined type: [**NextSubpath**](#nextsubpath) returns a [**SubPath**](Types/SubPath), [**NextMarker**](#nextmarker) a [**Marker**](Types/Marker), and so on. The *direct form* has the same name, takes variables for the results, and returns the number of points as a **Long**, as the C++ class does. In both forms the number of points is 0 when the iteration has come to its end. A call that fails records its status in **LastResult**, as every object of the package does. See [**GdiPlusBase**](GdiPlusBase). + > [!WARNING] 0.9.45.0 -> **NextSubpathPath** and **NextMarkerPath** do not work. The compiler reports a code generation error in **NextSubpathPath** whenever it compiles a project that uses **PathIterator**, though the other members still work when the program is run from the IDE. A program that calls the UDT form of either method stops at the call. The direct form returns 0 and a path that holds no GDI+ object, which stops the program at the first use. To get the points of a figure or a section, take the indexes from **NextSubpath** or **NextMarker** and read the points with **Enumerate**. +> **NextSubpathPath** and **NextMarkerPath** do not work. The compiler reports a code generation error in **NextSubpathPath** whenever it compiles a project that uses **PathIterator**, though the other members still work when the program is run from the IDE. A program that calls the UDT form of either method stops at the call, and so does one that calls the direct form of **NextMarkerPath**. The direct form of **NextSubpathPath** returns 0 and a path that holds no GDI+ object. To get the points of a figure or a section, take the indexes from **NextSubpath** or **NextMarker** and read the points with **Enumerate**. ## Creating @@ -54,6 +55,7 @@ Syntax: *iterator*.**HasCurve** **As Boolean** **True** when the path has at least one Bézier curve. A path that has been through [**Flatten**](GraphicsPath#flatten) has none. + ```tb check_build Dim user As GdiPlusUser = GdiPlusUser() Dim p As GraphicsPath = GraphicsPath() @@ -66,13 +68,16 @@ p.AddEllipse 40, 40, 10, 10 Dim it As PathIterator = PathIterator(p) Debug.Print it.Count & " " & it.SubpathCount & " " & it.HasCurve ' 19 3 True ``` + ### Rewind {: .no_toc } Syntax: *iterator*.**Rewind** **As GpStatus** + Puts all the positions back at the start of the path. + ## Figures @@ -89,10 +94,11 @@ Syntax: *iterator*.**NextSubpath(** *startIndex*, *endIndex*, *isClosed* **)** * *isClosed* : *required* A **Boolean** variable that receives **True** when the figure is closed. -Returns the next figure. The UDT form returns a [**SubPath**](Types/SubPath) with the same values and the number of points in its field *resultCount*. The direct form returns the number of points. At the end of the path both give 0 and leave the indexes as they were. +Returns the next figure. The UDT form returns a [**SubPath**](Types/SubPath) with the same values and the number of points in its field *resultCount*. The direct form returns the number of points. At the end of the path both give 0. The path of the first example has three figures. The rectangle is closed by nature, the line is open, and the ellipse is closed: + ```tb check_build Dim user As GdiPlusUser = GdiPlusUser() Dim p As GraphicsPath = GraphicsPath() @@ -113,9 +119,11 @@ Loop ' 4..5 False ' 6..18 True ``` + The direct form does the same with variables: + ```tb check_build Dim user As GdiPlusUser = GdiPlusUser() Dim p As GraphicsPath = GraphicsPath() @@ -131,6 +139,7 @@ Debug.Print n & " " & first & " " & last & " " & closed ' 2 4 5 False n = it.NextSubpath(first, last, closed) Debug.Print n ' 0 ``` + ### NextSubpathPath {: .no_toc } @@ -152,6 +161,7 @@ Syntax: *iterator*.**NextMarker(** *startIndex*, *endIndex* **)** **As Long** Returns the next section of the path, which ends at a marker set with [**SetMarker**](GraphicsPath#setmarker), or at the end of the path. The UDT form returns a [**Marker**](Types/Marker). Sections are not the same as figures: a marker can fall in the middle of a figure, and a section can hold several figures. + ```tb check_build Dim user As GdiPlusUser = GdiPlusUser() Dim p As GraphicsPath = GraphicsPath() @@ -171,6 +181,7 @@ Loop ' 0..3 ' 4..18 ``` + ### NextMarkerPath {: .no_toc } @@ -198,8 +209,11 @@ Syntax: *iterator*.**NextPathType(** *pathType*, *startIndex*, *endIndex* **)** Returns the next group of points of the same type, within the figure that **NextSubpath** returned last. The first group includes the start point of the figure. The UDT form returns a [**PathType**](Types/PathType). + Call **NextSubpath** first: before it, and after the last group of a figure, the call returns 0. Each figure then needs its own call of **NextSubpath**. + + ```tb check_build Dim user As GdiPlusUser = GdiPlusUser() Dim p As GraphicsPath = GraphicsPath() @@ -217,6 +231,7 @@ Loop ' 1: 0..1 ' 3: 1..4 ``` + ## Copying points @@ -233,14 +248,18 @@ Syntax: *iterator*.**Enumerate(** *points*(), *types*(), [ *startIndex*, *endInd *types* : *required* A dynamic array of [**PathPointType**](Types/PathPointType) that receives their types. + *startIndex*, *endIndex* : *optional* The first and the last point to copy, counted from 0. Both default to -1, which copies the whole path. Giving only one of them is refused with **InvalidParameter**. + Copies the points and their types. The UDT form returns a [**TypedPoints**](Types/TypedPoints), a structure that holds an array of [**TypedPoint**](Types/TypedPoint) and the count in *resultCount*. The direct form sizes the two arrays, with indexes from 1, and returns the count. + > [!NOTE] 0.9.45.0 > On an empty path both forms raise run-time error -2147467259, *Unspecified error*. Check **Count** first. + ```tb check_build Dim user As GdiPlusUser = GdiPlusUser() Dim p As GraphicsPath = GraphicsPath() @@ -254,6 +273,7 @@ Debug.Print all.resultCount & " " & UBound(all.points) ' 3 3 Dim part As TypedPoints = it.Enumerate(1, 2) Debug.Print part.resultCount & " " & part.points(1).pos.X ' 2 10 ``` + ### CopyData {: .no_toc } @@ -262,7 +282,9 @@ Syntax: *iterator*.**CopyData(** *startIndex*, *endIndex* **)** **As TypedPoints Syntax: *iterator*.**CopyData(** *points*(), *types*(), *startIndex*, *endIndex* **)** **As Long** + Deprecated. It does what **Enumerate** does with the two indexes; use **Enumerate**. + ## Example diff --git a/docs/Reference/Contributed/GdiPlus/Pen.md b/docs/Reference/Contributed/GdiPlus/Pen.md index 56a7ac54..a67790d7 100644 --- a/docs/Reference/Contributed/GdiPlus/Pen.md +++ b/docs/Reference/Contributed/GdiPlus/Pen.md @@ -39,8 +39,11 @@ Each form can also be written with **New**: *pen* **= New Pen**( ... ). The two *unit* : *optional* A [**GpUnit**](Enumerations/GpUnit) for the unit of *width*. The default is **UnitWorld**. + With **UnitWorld**, the width is in the units of the drawing surface, so a transformation of the [**Graphics**](Graphics) scales the line: after **ScaleTransform** 3, 3 a pen of width 1 draws a line 3 pixels wide. With **UnitPixel**, the pen is 1 pixel wide whatever the transformation. + + > [!WARNING] 0.9.45.0 > The *rgb* form reads its **Long** as `&H00RRGGBB`, not as a VB colour, and drops the alpha: `Pen(vbRed)` and `Pen(RGB(255, 0, 0))` draw in blue. [**IBrush**](IBrush#colours) lists what else is not translated. The same holds for [**Set**](#set) and [**RGB**](#rgb) with a **Long**. Use the **Color** forms. @@ -90,7 +93,9 @@ Debug.Print column Syntax: *copy* **=** *pen*.**Clone**( ) + Returns a new **Pen** with the same settings. Changing the copy does not change the original. + ## General @@ -110,7 +115,9 @@ Returns a [**GpPenType**](Enumerations/GpPenType) value for what the pen paints Syntax: *pen*.**Color** [ **=** *color* ] + Gets or sets the colour of the pen, as a **Color**. Setting it turns a pen that was made from another kind of brush into a solid colour pen. For a pen that is not of a solid colour, the value read is 0. + ### RGB @@ -130,8 +137,11 @@ Sets the colour and the width in one call. The *rgb* form takes `&H00RRGGBB`. Syntax: *pen*.**Brush** [ **=** *brush* ] + Gets or sets the [**Brush**](IBrush) that the pen paints with. Reading it returns a new brush of the same class as the one the pen was made from. + + > [!NOTE] 0.9.45.0 > A property that returns an object, such as **Brush**, **CustomStartCap** and **CustomEndCap**, returns **Nothing** while **LastResult** is not **Ok**, even when the failure was an earlier call. Call **ClearResult** first. @@ -202,6 +212,7 @@ Syntax: *pen*.**CustomStartCap** [ **=** *cap* ] Gets or sets a [**CustomLineCap**](CustomLineCap), or an [**ArrowCap**](ArrowCap), for the start of a line. Setting one makes the start cap **LineCapCustom**. Reading returns a cap object, or **Nothing** when the pen has none. + > [!WARNING] 0.9.45.0 > A pen with an **ArrowCap** returns an **ArrowCap** from **CustomStartCap** that is not usable: its members report **InvalidParameter** in **LastResult** and return 0. A plain **CustomLineCap** is returned correctly. @@ -211,9 +222,11 @@ Syntax: *pen*.**CustomEndCap** [ **=** *cap* ] Sets a [**CustomLineCap**](CustomLineCap), or an [**ArrowCap**](ArrowCap), for the end of a line, and makes the end cap **LineCapCustom**. + > [!WARNING] 0.9.45.0 -> Reading **CustomEndCap** raises error 91 and returns **Nothing**, and it replaces the custom start cap of the pen with the end cap: a failed read changes the pen. Only assigning works. +> Reading **CustomEndCap** raises error 91, or error 438 when the pen has a custom start cap too, and returns **Nothing**. Only assigning works. + The example draws a line with a round start cap and a flat end cap. **#** is a pixel of the pen. ```tb check_build check_run @@ -251,6 +264,7 @@ Debug.Print linePen.StartCap & " " & linePen.EndCap ' 1 19 3 ' 2 2 ``` + ## Dashes @@ -290,6 +304,7 @@ Syntax: *count* **=** *pen*.**CompoundArrayCount** Returns the number of values of the compound array, 0 for a pen that is not compound. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() @@ -346,6 +361,7 @@ Debug.Print row ' 4 ' ..##....##.. ``` + ## Joins and alignment @@ -367,6 +383,7 @@ Syntax: *pen*.**Alignment** [ **=** *alignment* ] Gets or sets the [**GpPenAlignment**](Enumerations/GpPenAlignment). With **PenAlignmentCenter**, the default, the line of a shape is centred on its edge. With **PenAlignmentInset**, the whole width of the pen lies inside the shape. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() @@ -416,6 +433,7 @@ Next ' .............. ' .............. ``` + ## Transformations @@ -427,6 +445,7 @@ Syntax: *pen*.**Transform** [ **=** *matrix* ] Sets the transformation of the pen to a [**Matrix**](Matrix). Reading it should return a copy of the matrix. + > [!NOTE] 0.9.45.0 > Reading **Transform** always fails: it returns **Nothing** and **LastResult** becomes **InvalidParameter**. Keep the matrix that was set. @@ -462,6 +481,7 @@ Syntax: *pen*.**RotateTransform**( *angle* [, *order*] ) Rotates the pen by *angle* degrees clockwise. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() @@ -495,6 +515,7 @@ Debug.Print column ' 2 True ' .........##......... ``` + ## See Also diff --git a/docs/Reference/Contributed/GdiPlus/PrivateFontCollection.md b/docs/Reference/Contributed/GdiPlus/PrivateFontCollection.md index ea7073dc..2c2d6eb5 100644 --- a/docs/Reference/Contributed/GdiPlus/PrivateFontCollection.md +++ b/docs/Reference/Contributed/GdiPlus/PrivateFontCollection.md @@ -20,6 +20,7 @@ Syntax: *collection* **= PrivateFontCollection**( ) Syntax: *collection* **= New PrivateFontCollection** + > [!WARNING] 0.9.45.0, BETA 1005 > Creating a **PrivateFontCollection** raises run-time error 438, *Object doesn't support this property or method*, and the result is **Nothing**. The statement `Debug.Assert TypeOf (Me) Is PrivateFontCollection` in the class raises it. Even with that statement removed, the constructor does not store the native collection it creates, so the object has no handle and **AddFontFile**, **AddMemoryFont** and the members of **FontCollection** fail with **InvalidParameter**. The class cannot be used until a version of the package corrects both. diff --git a/docs/Reference/Contributed/GdiPlus/RedEyeCorrection.md b/docs/Reference/Contributed/GdiPlus/RedEyeCorrection.md index 9c9a7aba..2bfd1979 100644 --- a/docs/Reference/Contributed/GdiPlus/RedEyeCorrection.md +++ b/docs/Reference/Contributed/GdiPlus/RedEyeCorrection.md @@ -25,10 +25,15 @@ Each form is also available as **New RedEyeCorrection**( ... ) with the same arg *params* : *required* A [**RedEyeCorrectionParams**](Types/RedEyeCorrectionParams): the number of areas and the memory that holds their rectangles. -A constructor that fails creates an unusable object whose **LastResult** tells why. The effect created without arguments has no areas, and [**Bitmap.ApplyEffect**](Bitmap#applyeffect) refuses to apply it: the call returns 2 (**InvalidParameter**). +A constructor that fails creates an unusable object whose **LastResult** tells why. + +The effect created without arguments has no areas, and [**Bitmap.ApplyEffect**](Bitmap#applyeffect) refuses to apply it: the call returns 2 (**InvalidParameter**). + + + > [!WARNING] 0.9.45.0 -> The form with *params* does not work. In a 64-bit program it stops the program with an access violation, and in a 32-bit program it sets **LastResult** to 2 (**InvalidParameter**), and so does a later **ApplyEffect** with the effect. The parameters are not set in either case. Setting [**Parameters**](#parameters) on an effect fails the same way. +> In a 64-bit program the form with *params* stops the program with an access violation. A 32-bit program is not affected. To work in both, create the effect without arguments and set [**Parameters**](#parameters), which works in both. ## Parameters @@ -38,8 +43,9 @@ Syntax: *params* **=** *fx*.**Parameters**( *size* ) Gets or sets the [**RedEyeCorrectionParams**](Types/RedEyeCorrectionParams). The setter creates the GDI+ effect again and sets the parameters on the new one, with the size `LenB(Of RedEyeCorrectionParams)` plus the size of the rectangles. + > [!WARNING] 0.9.45.0 -> The getter returns zeros and sets **LastResult** to 2 (**InvalidParameter**) unless it is given *size*. See [**Effect**](Effect#parameters). +> The getter returns zeros and sets **LastResult** to 2 (**InvalidParameter**) unless it is given *size*. On an effect that has areas it does so even with *size*, so the areas cannot be read back: keep the **RedEyeCorrectionParams** that was set. See [**Effect**](Effect#parameters). The areas are described by a structure whose methods allocate and fill the rectangles; the page of [**RedEyeCorrectionParams**](Types/RedEyeCorrectionParams) shows them. diff --git a/docs/Reference/Contributed/GdiPlus/Sharpen.md b/docs/Reference/Contributed/GdiPlus/Sharpen.md index e2e2dd3f..e733295d 100644 --- a/docs/Reference/Contributed/GdiPlus/Sharpen.md +++ b/docs/Reference/Contributed/GdiPlus/Sharpen.md @@ -43,6 +43,7 @@ Syntax: *params* **=** *fx*.**Parameters**( *size* ) Gets or sets the [**SharpenParams**](Types/SharpenParams). + > [!WARNING] 0.9.45.0 > The getter returns zeros and sets **LastResult** to 2 (**InvalidParameter**) unless it is given *size*, the size of the structure: `fx.Parameters(LenB(Of SharpenParams))`. See [**Effect**](Effect#parameters). diff --git a/docs/Reference/Contributed/GdiPlus/SolidBrush.md b/docs/Reference/Contributed/GdiPlus/SolidBrush.md index cfc35c9f..b4127729 100644 --- a/docs/Reference/Contributed/GdiPlus/SolidBrush.md +++ b/docs/Reference/Contributed/GdiPlus/SolidBrush.md @@ -30,6 +30,7 @@ Syntax: *brush* **= New SolidBrush**( *rgb* ) *rgb* : *required* A **Long** for the colour, as `&H00RRGGBB`. The alpha is always 255. + > [!WARNING] 0.9.45.0 > The *rgb* form reads its **Long** as `&H00RRGGBB`, not as a VB colour: `SolidBrush(vbRed)` and `SolidBrush(RGB(255, 0, 0))` paint blue. The *rgb* form drops the alpha value, and a **CColor** member passed to it binds to it, so `SolidBrush(CColor.Transparent)` fills opaque white. [**IBrush**](IBrush#colours) lists what else is not translated. Use the **Color** form. @@ -55,7 +56,9 @@ Debug.Print Hex$(bmp.Pixel(1, 1).argb) Syntax: *copy* **=** *brush*.**Clone**( ) + Returns a new **SolidBrush** with the same colour. Changing the copy does not change the original. + ## Colour @@ -71,6 +74,7 @@ Syntax: *brush*.**RGB** [ **=** *rgb* ] Gets or sets the colour as a **Long** in the form `&H00RRGGBB`. Reading it drops the alpha value, and writing it sets an opaque colour. + > [!WARNING] 0.9.45.0 > **RGB** reads and writes `&H00RRGGBB` and not a VB colour, so assigning `vbRed` makes the brush blue. Use [**Color**](#color). diff --git a/docs/Reference/Contributed/GdiPlus/StringFormat.md b/docs/Reference/Contributed/GdiPlus/StringFormat.md index 9eb7fec7..b4cfa6a1 100644 --- a/docs/Reference/Contributed/GdiPlus/StringFormat.md +++ b/docs/Reference/Contributed/GdiPlus/StringFormat.md @@ -41,8 +41,11 @@ Syntax: *format* **= GenericDefaultStringFormat**( ) Syntax: *format* **= GenericTypographicStringFormat**( ) + **GenericDefaultStringFormat** returns the format that GDI+ uses when none is given. It has no flags, near alignment and trimming to a whole character. **GenericTypographicStringFormat** returns a format for typographic layout, with the flags **NoFitBlackBox**, **LineLimit** and **NoClip** and no trimming. + + > [!NOTE] 0.9.45.0 > Both functions return the shared generic format of GDI+, not a copy. Changing the object changes the generic format for the whole program, and a later call returns the changed format. Call [**Clone**](#clone) and change the copy instead. @@ -122,6 +125,7 @@ Syntax: *format*.**Alignment** [ **=** *alignment* ] Returns or sets the alignment. + > [!WARNING] 0.9.45.0 > Reading **Alignment** fails. It returns **StringAlignmentNear** (0) whatever the alignment is, and sets **LastResult** to **InvalidParameter**. The read-only properties **IsAlignmentNear**, **IsAlignmentCenter** and **IsAlignmentFar** are wrong for the same reason: **IsAlignmentNear** always returns **True**, and the other two always return **False**. Setting the alignment works, with the property or with the **SetAlignment...** methods, so keep the value in a variable if the program needs to read it back. If the error strategy is **RaiseErrors**, the read raises an error. > @@ -344,6 +348,7 @@ Syntax: *status* **=** *format*.**GetTabStops**( *count*, *firstTabOffset*, *tab The first form returns the tab stops as a one-based array of **Single** values. The second form fills an array that the caller has made and returns a [**GpStatus**](Enumerations/GpStatus). + > [!WARNING] 0.9.45.0 > The first form of **GetTabStops** does not return the first tab offset: the variable passed for *firstTabOffset* receives 0, whatever offset was set. The array it returns is correct. The second form returns the offset correctly, so use it when the offset is needed. > @@ -368,6 +373,7 @@ The first form returns the tab stops as a one-based array of **Single** values. Digit substitution draws the digits 0 to 9 of a string with the digits of another script, such as Arabic-Indic digits. The language says which script. The methods are [**GpStringDigitSubstitute**](Enumerations/GpStringDigitSubstitute) values: **StringDigitSubstituteUser** follows the user's regional settings, **StringDigitSubstituteNone** turns the substitution off, **StringDigitSubstituteNational** uses the digits of the official language of the locale and **StringDigitSubstituteTraditional** uses the digits of the user's native script, which can be a different language. A language identifier is an **Integer**, such as 1033 for US English. + > [!WARNING] 0.9.45.0 > Setting one half of the substitution resets the other. **SetDigitSubstitutionUser**, **SetDigitSubstitutionNone**, **SetDigitSubstitutionNational** and **SetDigitSubstitutionTraditional** set the language to 0, and setting **DigitSubstitutionLanguage** sets the method to **StringDigitSubstituteUser**. To change one half and keep the other, set the method with **DigitSubstitutionMethod**, or set both with **SetDigitSubstitution**. > @@ -432,6 +438,7 @@ Each function returns **True** when the method is the value in its name. A format can name up to 32 ranges of characters in a string. [**Graphics.MeasureCharacterRanges**](Graphics) then returns one [**Region**](Region) for each range, which covers the space that the characters of the range take when the string is laid out. A range is a [**CharacterRange**](Types/CharacterRange). + > [!NOTE] timeless > GDI+ accepts at most 32 ranges. A call with more returns **ValueOverflow** and leaves the earlier ranges in place. diff --git a/docs/Reference/Contributed/GdiPlus/TextureBrush.md b/docs/Reference/Contributed/GdiPlus/TextureBrush.md index 67d9c760..d6e0ec46 100644 --- a/docs/Reference/Contributed/GdiPlus/TextureBrush.md +++ b/docs/Reference/Contributed/GdiPlus/TextureBrush.md @@ -43,9 +43,11 @@ Each form of **TextureBrush** can also be written with **New**: *brush* **= New *imageAttributes* : *optional* The [**ImageAttributes**](ImageAttributes) that are applied to the image when the brush draws. The default is none. + > [!NOTE] 0.9.45.0 > The constructor with **Long** values has a seventh parameter, a **Long** that is ignored, so that a call with six numbers is read as the one with **Single** values. **TextureBrushI** is the function for it, and it is the only form without **New**. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() @@ -76,6 +78,7 @@ Debug.Print brush.LastResult & " " & brush.Type & " " & brush.WrapMode ' BWBWBW ' 0 2 0 ``` + ### Clone @@ -85,6 +88,7 @@ Returns a new **TextureBrush** with the same tile, wrap mode and transformation. ## The tile + The example takes the 2-by-2 square at the lower right of a 4-by-4 image as the tile. ```tb check_build check_run @@ -115,6 +119,7 @@ Debug.Print whole.Width & " " & whole.Height ' FF787800 FFB47800 FF787800 FFB47800 ' 2 2 ``` + ### Image @@ -130,6 +135,7 @@ Syntax: *brush*.**WrapMode** [ **=** *wrapMode* ] Gets or sets what the brush does outside the first tile: a [**GpWrapMode**](Enumerations/GpWrapMode) value. + | Value | Outside the first tile | |:------|:-----------------------| | **WrapModeTile** | The tile repeats. | @@ -172,6 +178,7 @@ Next ' RG.... ' BW.... ``` + ## Transformations @@ -183,6 +190,7 @@ Syntax: *brush*.**Transform** [ **=** *matrix* ] Sets the transformation of the brush to a [**Matrix**](Matrix). Reading it should return a copy of the matrix. + > [!NOTE] 0.9.45.0 > Reading **Transform** always fails: it returns **Nothing** and **LastResult** becomes **InvalidParameter**. Use [**GetTransform**](#gettransform). @@ -190,7 +198,9 @@ Sets the transformation of the brush to a [**Matrix**](Matrix). Reading it shoul Syntax: *brush*.**GetTransform**( *matrix* ) + Copies the transformation of the brush into *matrix*, a **Matrix** that the caller created. + ### ResetTransform @@ -236,6 +246,7 @@ Syntax: *brush*.**ShearTransform**( *shearX*, *shearY* [, *order*] ) Shears the tiles: *shearX* is the amount that x grows for each unit of y, and *shearY* the amount that y grows for each unit of x. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() @@ -269,6 +280,7 @@ Next ' GRGRGR ' WBWBWB ``` + ## Type diff --git a/docs/Reference/Contributed/GdiPlus/Tint.md b/docs/Reference/Contributed/GdiPlus/Tint.md index a16a973f..5e71d1e3 100644 --- a/docs/Reference/Contributed/GdiPlus/Tint.md +++ b/docs/Reference/Contributed/GdiPlus/Tint.md @@ -43,6 +43,7 @@ Syntax: *params* **=** *fx*.**Parameters**( *size* ) Gets or sets the [**TintParams**](Types/TintParams). + > [!WARNING] 0.9.45.0 > The getter returns zeros and sets **LastResult** to 2 (**InvalidParameter**) unless it is given *size*, the size of the structure: `fx.Parameters(LenB(Of TintParams))`. See [**Effect**](Effect#parameters). diff --git a/docs/Reference/Contributed/GdiPlus/Types/Color.md b/docs/Reference/Contributed/GdiPlus/Types/Color.md index 7ce42b36..6946e6b4 100644 --- a/docs/Reference/Contributed/GdiPlus/Types/Color.md +++ b/docs/Reference/Contributed/GdiPlus/Types/Color.md @@ -11,7 +11,10 @@ has_toc: false A colour with an alpha (opacity) channel and red, green and blue channels of 8 bits each. + **Color** is the colour that pens, brushes, **Clear** and the drawing methods take, and that **GetPixel** returns. It holds one **Long** field, **argb**, laid out as `&HAARRGGBB`: alpha in the top byte, then red, green and blue. Alpha 255 is opaque and 0 is fully transparent. It mirrors the `Color` class of the GDI+ C++ wrapper. + + | Byte of **argb** | Channel | Shift | |---|---|---| @@ -22,6 +25,7 @@ A colour with an alpha (opacity) channel and red, green and blue channels of 8 b The common named colours (`CColor.Red`, `CColor.AliceBlue`, ...) are in the enumeration [**CColor**](../Enumerations/CColor); each is an opaque colour in this layout, except **Transparent**. + > [!NOTE] 0.9.45.0 > A variable declared with `Dim c As Color` holds 0: a fully transparent black, not black. Drawing with it shows nothing. Create colours with the module function **Color**, which gives an opaque colour. @@ -33,6 +37,7 @@ Syntax: **Color**( *r*, *g*, *b* ) \ Syntax: **Color**( *a*, *r*, *g*, *b* ) \ Syntax: **Color**( *argb* ) + The module functions **Color** and **NoColor** return a new colour. **Color**( ) returns opaque black, `&HFF000000`. **NoColor**( ) returns 0, which is transparent black and is what **IsNull** tests for. @@ -45,7 +50,9 @@ The module functions **Color** and **NoColor** return a new colour. *argb* : *required* A **Long** that is stored as it is, in the layout `&HAARRGGBB`. A member of [**CColor**](../Enumerations/CColor) is accepted. + + > [!NOTE] 0.9.45.0 > The channel arguments are masked with `&HFF` and are not checked: `Color(300, -1, 0, 256)` is `&H2CFF0000`. No error is raised. @@ -68,9 +75,11 @@ Debug.Print Hex$(Color(300, -1, 0, 256).Value) ' 2CFF0000 Syntax: *color*.**Alpha** [ = *value* ] As **Integer** + Returns or sets one channel. **A**, **R**, **G** and **B** are the same four properties under shorter names. The getters return an **Integer** from 0 to 255. The setters take a **Byte**: a value outside 0 to 255 raises error 6, *Overflow*, and the colour is not changed. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() @@ -90,6 +99,7 @@ Debug.Print Err.Number ' 6 Syntax: *color*.**Value** [ = *argb* ] As **Long** + Returns or sets the colour as `&HAARRGGBB`. The field **argb** holds the same number. ### IsNull @@ -103,6 +113,7 @@ Read-only. **True** when the value is 0. Syntax: *color*.**IsTransparent** As **Boolean** Read-only. **True** only when the value is exactly `&H00FFFFFF`, the value of **CColor.Transparent**. A colour with alpha 0 and other channels is not reported as transparent. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() @@ -122,17 +133,23 @@ Windows and Visual Basic keep a colour as a `COLORREF`, `&H00BBGGRR`, with red i Syntax: *color*.**RGB** As **Long** + Read-only. Returns the colour as `&H00RRGGBB`, without the alpha channel. + ### BGR Syntax: *color*.**BGR** [ = *colorref* ] As **Long** + Returns the colour as a `COLORREF`, `&H00BBGGRR`, which is the value of the `RGB` function and of a Visual Basic colour constant such as `vbRed`. Setting it takes a `COLORREF` and gives an opaque colour. This is the conversion to use for Windows colours. + + > [!NOTE] 0.9.45.0 -> **BGR** converts the colour values only. The system colours of Visual Basic, such as `vbButtonFace` (`&H8000000F`), are not translated to the colours that Windows currently uses: the setter gives a nearly black blue. Translate them first with the Windows function `OleTranslateColor`. +> **BGR** converts the colour values only. The system colours of Visual Basic, such as `vbButtonFace` (`&H8000000F`), are not translated to the colours that Windows currently uses: the setter gives a nearly black red. Translate them first with the Windows function `OleTranslateColor`. + > [!NOTE] 0.9.45.0 > The methods of the package that take a plain **Long** colour (such as the constructors of **SolidBrush** and **Pen**) read it as `&H00RRGGBB`, not as a `COLORREF`, and make it opaque. `SolidBrush(vbRed)` is blue. Pass `Color(...)`, or a **Color** whose **BGR** you have set, to draw a Visual Basic colour. @@ -140,6 +157,7 @@ Returns the colour as a `COLORREF`, `&H00BBGGRR`, which is the value of the `RGB Syntax: *color*.**AsCOLORREF**( ) As **Long** + > [!WARNING] 0.9.45.0 > **AsCOLORREF** returns `&H00RRGGBB`, the same as **RGB**, and not a `COLORREF`: red and blue are the wrong way round for Windows. Use **BGR**. @@ -148,6 +166,7 @@ Syntax: *color*.**AsCOLORREF**( ) As **Long** Syntax: **FromCOLORREF**( *colorref* ) As **Color** \ Syntax: *color*.**SetFromCOLORREF**( *colorref* ) + > [!WARNING] 0.9.45.0 > Both read the argument as `&H00RRGGBB` and swap red and blue: `FromCOLORREF(vbRed)` is blue. To convert a `COLORREF`, set **BGR** on a colour, or write `Color(255, c And &HFF, (c And &HFF00&) \ &H100, (c And &HFF0000) \ &H10000)`. @@ -172,26 +191,34 @@ Syntax: *color*.**GetRGBB**( *r* As **Byte**, *g* As **Byte**, *b* As **Byte** ) Syntax: *color*.**GetRGBI**( *r* As **Integer**, *g* As **Integer**, *b* As **Integer** ) \ Syntax: *color*.**GetRGBL**( *r* As **Long**, *g* As **Long**, *b* As **Long** ) + Return the three channels, 0 to 255, in variables of the given type. + ### GetRGBS, GetRGBD Syntax: *color*.**GetRGBS**( *r* As **Single**, *g* As **Single**, *b* As **Single** ) \ Syntax: *color*.**GetRGBD**( *r* As **Double**, *g* As **Double**, *b* As **Double** ) + Return the three channels as percentages: 0 to 100. + ### SetRGB Syntax: *color*.**SetRGB**( *r*, *g*, *b* ) + Sets the three channels from **Long** values, 0 to 255. A value outside the range is clamped to it. The alpha channel is not changed. + ### SetRGBD Syntax: *color*.**SetRGBD**( *r*, *g*, *b* ) + Sets the three channels from **Double** values given as percentages, 0 to 100, clamped to that range. The alpha channel is not changed. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() @@ -215,7 +242,9 @@ Debug.Print Hex$(c.Value) ' 80FF0000 Syntax: *color*.**AsHexRGB**( ) As **String** \ Syntax: *color*.**AsHexRGBA**( ) As **String** + Return the colour as `#rrggbb`, or as `#rrggbbaa`, in upper case hexadecimal. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() @@ -233,14 +262,18 @@ Hue is in degrees, from 0 up to but not including 360. Saturation, lightness and Syntax: *color*.**GetHSL**( *h* As **Single**, *s* As **Single**, *l* As **Single** ) \ Syntax: *color*.**GetHSLA**( *h* As **Single**, *s* As **Single**, *l* As **Single**, *a* As **Single** ) + Return the hue, saturation and lightness, and for **GetHSLA** the alpha as well. + ### SetHSL, SetHSLA Syntax: *color*.**SetHSL**( *h*, *s*, *l* ) \ Syntax: *color*.**SetHSLA**( *h*, *s*, *l*, *a* ) + Set the channels from a hue, saturation and lightness, clamped to their ranges. **SetHSL** keeps the alpha of the colour; **SetHSLA** sets it from *a*. A colour declared with `Dim` has alpha 0, so call **SetHSLA**, or set **Alpha**, to make it visible. + ### RotatedHue, RotateHue @@ -249,6 +282,7 @@ Syntax: *color*.**RotateHue**( *degrees* ) **RotatedHue** returns the colour with its hue turned by *degrees*, which may be negative or above 360. **RotateHue** changes the colour itself. + > [!WARNING] 0.9.45.0 > For any angle other than 0 the result has alpha 0, so it is invisible: `Color(255, 0, 0).RotatedHue(120)` is `&H0000FF00`. After **RotateHue** the colour itself is transparent. Set **Alpha** again on the result. @@ -275,6 +309,7 @@ Debug.Print Hex$(green.Value) ' FF00FF00 Syntax: *color*.**Inverse** [ = *source* ] As **Color** + Returns the colour with each of red, green and blue replaced by 255 less its value: the negative of the colour. The alpha channel is kept. Setting it makes the colour the inverse of *source*. ### Invert @@ -282,6 +317,7 @@ Returns the colour with each of red, green and blue replaced by 255 less its val Syntax: *color*.**Invert** Replaces the colour by its inverse. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() @@ -300,13 +336,17 @@ Debug.Print Hex$(z.Value) ' FFF5EBE1 Syntax: **MakeARGB**( *a*, *r*, *g*, *b* ) As **Long** + Returns `&HAARRGGBB` from four **Long** values. Each is masked with `&HFF`. + ### HueToValue Syntax: **HueToValue**( *hue*, *m1*, *m2* ) As **Single** + The helper step of the conversion from hue to a channel, as in the HSL algorithm of CSS. *hue* is a fraction of a full turn from -1 to 1; *m1* and *m2* are the two lightness-derived values, 0 to 1, and the result is in the same range. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() diff --git a/docs/Reference/Contributed/GdiPlus/Types/ColorLUTParams.md b/docs/Reference/Contributed/GdiPlus/Types/ColorLUTParams.md index d515809a..9cc8f7a9 100644 --- a/docs/Reference/Contributed/GdiPlus/Types/ColorLUTParams.md +++ b/docs/Reference/Contributed/GdiPlus/Types/ColorLUTParams.md @@ -30,6 +30,7 @@ Syntax: *params* **= ColorLUTParams**( *lutB*, *lutG*, *lutR*, *lutA* ) *lutB*, *lutG*, *lutR*, *lutA* : *required* Arrays of **Byte** with 256 elements, any lower bound. If one of the arrays has another length, the function returns a structure of zeros. + > [!WARNING] 0.9.45.0 > The function copies the alpha table from the array *lutB*. The structure it returns maps alpha through the blue table. Fill the fields yourself instead, as the example does. diff --git a/docs/Reference/Contributed/GdiPlus/Types/ColorMatrix.md b/docs/Reference/Contributed/GdiPlus/Types/ColorMatrix.md index ef2f402f..cdca2289 100644 --- a/docs/Reference/Contributed/GdiPlus/Types/ColorMatrix.md +++ b/docs/Reference/Contributed/GdiPlus/Types/ColorMatrix.md @@ -28,6 +28,7 @@ The array is indexed by the **output** component first and the **input** compone | **m**(**j**, 4) | adds the value, as a fraction of 255, to channel **j** | | **m**(4, 4) | 1 | + > [!NOTE] 0.9.45.0 > The two indexes are the reverse of the C++ `ColorMatrix`, where `m[i][j]` has the input row first: the array is stored so that its memory layout is the one GDI+ expects. To add a value to red, set **m**(0, 4), not `m[4][0]`. diff --git a/docs/Reference/Contributed/GdiPlus/Types/ColorPalette.md b/docs/Reference/Contributed/GdiPlus/Types/ColorPalette.md index d6e0d08f..b77bae70 100644 --- a/docs/Reference/Contributed/GdiPlus/Types/ColorPalette.md +++ b/docs/Reference/Contributed/GdiPlus/Types/ColorPalette.md @@ -32,6 +32,7 @@ Syntax: *pal* **= ColorPalette**( *flags*, *entries*( ) ) The function sets **flags** and sets **count** to the smaller of 256 and the length of *entries*. + > [!WARNING] 0.9.45.0 > The function does not copy the colours: every element of **entries** of the result is 0. Set **entries** one element at a time, as below. diff --git a/docs/Reference/Contributed/GdiPlus/Types/GdiplusStartupInput.md b/docs/Reference/Contributed/GdiPlus/Types/GdiplusStartupInput.md index 463094c0..59c00c92 100644 --- a/docs/Reference/Contributed/GdiPlus/Types/GdiplusStartupInput.md +++ b/docs/Reference/Contributed/GdiPlus/Types/GdiplusStartupInput.md @@ -13,6 +13,7 @@ The settings that GDI+ is started with. Pass a **GdiplusStartupInput** to the [**GdiPlusUser**](../GdiPlusUser) constructor to start GDI+ with other settings than the default ones. It mirrors the `GdiplusStartupInput` structure of GDI+ and has five fields and no methods. + A variable declared with `Dim` has every field 0, and its version 0 is not valid: a **GdiPlusUser** created from it fails with status 17, *UnsupportedGdiplusVersion*. Set **GdiplusVersion** before use, or take the structure from one of the module functions. ```tb check_build check_run @@ -27,6 +28,7 @@ settings.StartupParameters = GdiplusStartupNoSetRound Dim custom As GdiPlusUser = GdiPlusUser(settings) Debug.Print settings.StartupParameters ' 1 ``` + ## Fields @@ -42,6 +44,7 @@ A [**DebugEventProc**](#debugeventproc) that GDI+ calls with assertion and warni A flag that takes **True** or **False**. **True** stops GDI+ from creating its own background thread, and the program must then call the two functions of a [**GdiplusStartupOutput**](GdiplusStartupOutput) itself. Leave it **False** unless the program does that. + > [!NOTE] 0.9.45.0 > The constructor that takes only an input, `GdiPlusUser(settings)`, fails with status 2, *InvalidParameter*, when this flag is **True**, because it gives GDI+ nowhere to return the two functions. Use the constructor that also takes a **GdiplusStartupOutput**. @@ -59,10 +62,13 @@ Syntax: **GdiplusStartupInput**( [ *debugEventCallback* [, *suppressBackgroundTh Syntax: **GdiplusStartupInput**( *startupParameters* [, *debugEventCallback* [, *suppressBackgroundThread* [, *suppressExternalCodecs* ]]] ) \ Syntax: **GdiplusStartupInput**( *gdiplusVersion*, *startupParameters* [, *debugEventCallback* [, *suppressBackgroundThread* [, *suppressExternalCodecs* ]]] ) + The module function **GdiplusStartupInput** returns a structure with the fields set. The first form sets the version to 1, the second to 2, and the third to *gdiplusVersion*. + + > [!WARNING] 0.9.45.0 -> The forms cannot be told apart by an integer or an enumeration member as the first argument. `GdiplusStartupInput(GdiplusStartupNoSetRound)` returns version 1 with no startup parameters, as if the value were a callback, and `GdiplusStartupInput(3, GdiplusStartupDefault)` returns version 2. Set the fields one by one, as in the sample above. +> In a 32-bit program the forms cannot be told apart by an integer or an enumeration member as the first argument. `GdiplusStartupInput(GdiplusStartupNoSetRound)` returns version 1 with no startup parameters, as if the value were a callback, and `GdiplusStartupInput(3, GdiplusStartupDefault)` returns version 2 with the startup parameters 3. A 64-bit program gets the forms that the syntax lines show. Set the fields one by one, as in the sample above. ## DebugEventProc diff --git a/docs/Reference/Contributed/GdiPlus/Types/GdiplusStartupOutput.md b/docs/Reference/Contributed/GdiPlus/Types/GdiplusStartupOutput.md index 03c5f368..4f884e5c 100644 --- a/docs/Reference/Contributed/GdiPlus/Types/GdiplusStartupOutput.md +++ b/docs/Reference/Contributed/GdiPlus/Types/GdiplusStartupOutput.md @@ -17,6 +17,7 @@ A program that leaves the background thread on never needs this type. ## Fields + ### NotificationHook A [**NotificationHookProc**](#notificationhookproc). **0** until GDI+ has been started with the output. @@ -24,6 +25,7 @@ A [**NotificationHookProc**](#notificationhookproc). **0** until GDI+ has been s ### NotificationUnhook A [**NotificationUnhookProc**](#notificationunhookproc). **0** until GDI+ has been started with the output. + ## Delegates @@ -35,15 +37,18 @@ A [**NotificationUnhookProc**](#notificationunhookproc). **0** until GDI+ has be **Sub**( *token* As **ULONG** ). Stops it. Pass the token that the hook returned. + > [!WARNING] 0.9.45.0 -> The token is declared as a 32-bit **Long**, and in a 64-bit program GDI+ writes a 64-bit value into it. **NotificationHookProc** overwrites the four bytes after the variable. Use the module functions **NotificationHook** and **NotificationUnhook** below, whose token is a **LongPtr**. +> The token is declared as a 32-bit **Long**, and in a 64-bit program GDI+ writes a 64-bit value into it. **NotificationHookProc** overwrites the four bytes after the variable. Pass the first element of an array of two **Long** values, as in the sample below, so that the four bytes it overwrites belong to nothing else. ## Module functions Syntax: **NotificationHook**( *token* As **LongPtr** ) As **GpStatus** \ Syntax: **NotificationUnhook**( *token* As **LongPtr** ) -Call the two functions of GDI+ directly. They work only after GDI+ has been started with a [**GdiplusStartupInput**](GdiplusStartupInput) whose **SuppressBackgroundThread** is **True**, through the constructor that takes an output. + +> [!WARNING] 0.9.45.0 +> **NotificationHook** returns status 1, *GenericError*, and leaves the token 0, also after GDI+ was started with a [**GdiplusStartupInput**](GdiplusStartupInput) whose **SuppressBackgroundThread** is **True** through the constructor that takes an output. The function it declares is not the one in the output. Call the **NotificationHook** and **NotificationUnhook** fields of the output, as in the sample below. ```tb check_build check_run Dim settings As GdiplusStartupInput @@ -54,10 +59,11 @@ Dim user As GdiPlusUser = GdiPlusUser(settings, output) Debug.Print user.LastResult ' 0 Debug.Print output.NotificationHook <> 0 ' True Debug.Print output.NotificationUnhook <> 0 ' True -Dim token As LongPtr -Dim status As GpStatus = NotificationHook(token) +Dim slot(0 To 1) As Long +Dim status As GpStatus = output.NotificationHook(slot(0)) +Debug.Print status ' 0 ' ... the program's message loop runs here ... -NotificationUnhook token +output.NotificationUnhook slot(0) ``` ## See Also diff --git a/docs/Reference/Contributed/GdiPlus/Types/GpEncoderParameter.md b/docs/Reference/Contributed/GdiPlus/Types/GpEncoderParameter.md index f520f234..c3d03312 100644 --- a/docs/Reference/Contributed/GdiPlus/Types/GpEncoderParameter.md +++ b/docs/Reference/Contributed/GdiPlus/Types/GpEncoderParameter.md @@ -12,6 +12,7 @@ One parameter of an image encoder: an identifier, a type, and a list of values. Encoder parameters tell an encoder how to write a picture, for instance the quality of a JPEG. [**Image.GetEncoderParameterList**](../Image#getencoderparameterlist) returns the parameters an encoder accepts, and [**Image.Save**](../Image#save) takes an array of them. The identifiers come from the functions of [**ImageCodec**](../ImageCodec#encoder-parameters). The type mirrors the GDI+ [EncoderParameter](https://learn.microsoft.com/en-us/windows/win32/api/gdiplusimaging/ns-gdiplusimaging-encoderparameter) structure. It is 28 bytes long in a 32-bit program and 32 bytes in a 64-bit one. + > [!WARNING] 0.9.45.0 > **Image.Save** and **Image.GetEncoderParameterList** do not work, so a program cannot pass these parameters to an encoder or get them from one. See the warnings on [**Image**](../Image#save). diff --git a/docs/Reference/Contributed/GdiPlus/Types/GpImageCodecInfo.md b/docs/Reference/Contributed/GdiPlus/Types/GpImageCodecInfo.md index 4aa51cd4..80914476 100644 --- a/docs/Reference/Contributed/GdiPlus/Types/GpImageCodecInfo.md +++ b/docs/Reference/Contributed/GdiPlus/Types/GpImageCodecInfo.md @@ -12,6 +12,7 @@ A description of an image codec: its class ID, the format it handles, its names The codec lists of [**ImageCodec**](../ImageCodec#listing-the-codecs) are arrays of this type. It mirrors the GDI+ [ImageCodecInfo](https://learn.microsoft.com/en-us/windows/win32/api/gdiplusimaging/nl-gdiplusimaging-imagecodecinfo) class, and has the same layout. The text properties read strings in the buffer that GDI+ filled, so the buffer has to stay alive. + > [!WARNING] 0.9.45.0 > The text properties (**CodecName**, **DllName**, **FormatDescription**, **FilenameExtension** and **MimeType**) return their string with the terminating null character included, so `info.MimeType = "image/png"` is **False**. Remove it with `Replace(text, vbNullChar, "")`. diff --git a/docs/Reference/Contributed/GdiPlus/Types/GpPoint.md b/docs/Reference/Contributed/GdiPlus/Types/GpPoint.md index 2b66a7be..a3eec636 100644 --- a/docs/Reference/Contributed/GdiPlus/Types/GpPoint.md +++ b/docs/Reference/Contributed/GdiPlus/Types/GpPoint.md @@ -11,9 +11,14 @@ has_toc: false A point or two-dimensional vector with **Long** coordinates. + **GpPoint** is the point that the integer overloads of the drawing methods take. It is 8 bytes: the fields **X** and **Y**, followed by methods that read and change them. The floating-point counterpart is [**GpPointF**](GpPointF); a size is a [**GpSize**](GpSize) and a rectangle a [**GpRect**](GpRect). It mirrors the `Point` class of the GDI+ C++ wrapper. + + + A variable declared with `Dim` starts at (0, 0). + ## Creating @@ -22,6 +27,7 @@ Syntax: **GpPoint**( *x*, *y* ) \ Syntax: **GpPoint**( *point* ) \ Syntax: **GpPoint**( *size* ) + The module function **GpPoint** returns a new point. *x*, *y* @@ -34,6 +40,7 @@ The module function **GpPoint** returns a new point. : *required* A [**GpSize**](GpSize). The new point takes **X** from its **Width** and **Y** from its **Height**. With no arguments the point is (0, 0). + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() @@ -55,13 +62,17 @@ The coordinates, as **Long** fields. Assign them directly. Syntax: *point*.**Zero** As **Boolean** + Read-only. **True** when both coordinates are 0. + ### Transposed Syntax: *point*.**Transposed** As **GpPoint** + Read-only. Returns a point with **X** and **Y** exchanged. + ## Conversion @@ -69,20 +80,26 @@ Read-only. Returns a point with **X** and **Y** exchanged. Syntax: *point*.**AsGpPointF**( ) As **GpPointF** + Returns the same coordinates as **Single** values. + ### SetFrom Syntax: *point*.**SetFrom**( *source* ) + Sets both coordinates from a [**GpPointF**](GpPointF), rounded as the constructor does. + ### AsPOINT, SetFromPOINT Syntax: *point*.**AsPOINT**( ) As **GDIP_POINT** \ Syntax: *point*.**SetFromPOINT**( *source* As **GDIP_POINT** ) + Convert to and from a structure of two **Long** fields `x` and `y`, which is the layout of the Windows `POINT`. The structure is declared as **GDIP_POINT**. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() @@ -100,6 +117,7 @@ Debug.Print winPoint.x & "," & winPoint.y ' 3,-3 These change the point they are called on. + > [!NOTE] 0.9.45.0 > **Add** and **Sub** change the point they are called on. Before version 0.9.0.43 they returned the result and left the point alone. Use [**Sum**](#sum-diff) and [**Diff**](#sum-diff) to get a new point. @@ -112,9 +130,11 @@ Adds *other* to the point, or subtracts it. ### Transpose + Syntax: *point*.**Transpose** Exchanges **X** and **Y**. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() @@ -141,8 +161,11 @@ Returns the sum of the two vectors, or the point less *other*. Syntax: *point*.**DotProd**( *other* As **GpPoint** ) As **LongLong** + Returns the dot product, `X * other.X + Y * other.Y`. + + > [!NOTE] 0.9.45.0 > Each product is calculated as a **Long**, and a product above 2,147,483,647 raises error 6, *Overflow*, although the result is a **LongLong**. @@ -150,7 +173,9 @@ Returns the dot product, `X * other.X + Y * other.Y`. Syntax: *point*.**Equals**( *other* As **GpPoint** ) As **Boolean** + **True** when both coordinates equal those of *other*. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() diff --git a/docs/Reference/Contributed/GdiPlus/Types/GpPointF.md b/docs/Reference/Contributed/GdiPlus/Types/GpPointF.md index 2b399c7a..cbae2dfc 100644 --- a/docs/Reference/Contributed/GdiPlus/Types/GpPointF.md +++ b/docs/Reference/Contributed/GdiPlus/Types/GpPointF.md @@ -11,9 +11,14 @@ has_toc: false A point or two-dimensional vector with **Single** coordinates. + **GpPointF** is the point that the **Single** overloads of the drawing methods take. It is 8 bytes: the fields **X** and **Y**, followed by methods that read and change them. The integer counterpart is [**GpPoint**](GpPoint); a size is a [**GpSizeF**](GpSizeF) and a rectangle a [**GpRectF**](GpRectF). It mirrors the `PointF` class of the GDI+ C++ wrapper. + + + A variable declared with `Dim` starts at (0, 0). + ## Creating @@ -22,6 +27,7 @@ Syntax: **GpPointF**( *x*, *y* ) \ Syntax: **GpPointF**( *point* ) \ Syntax: **GpPointF**( *size* ) + The module function **GpPointF** returns a new point. A **GpPointF** has no constructor of the `New` form. *x*, *y* @@ -34,6 +40,7 @@ The module function **GpPointF** returns a new point. A **GpPointF** has no cons : *required* A [**GpSizeF**](GpSizeF). The new point takes **X** from its **Width** and **Y** from its **Height**. With no arguments the point is (0, 0). + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() @@ -56,15 +63,21 @@ The coordinates, as **Single** fields. Assign them directly. Syntax: *point*.**Zero** As **Boolean** + Read-only. **True** when both coordinates are exactly 0. + ### Length Syntax: *point*.**Length** [ = *value* ] As **Single** + Returns the length of the vector, the square root of **X** squared plus **Y** squared. Setting it scales the vector to the given length and keeps its direction. + + Setting **Length** to 0 sets both coordinates to 0, and the direction is lost. Setting the length of a zero vector, or of one shorter than twice [**REAL_EPSILON**](../Helpers#real_epsilon), changes nothing. + ### Normalized @@ -72,6 +85,7 @@ Syntax: *point*.**Normalized** As **GpPointF** Returns the vector scaled to length 1. + > [!NOTE] 0.9.45.0, BETA 1005 > A program that names **Normalized** does not link: the linker reports a code generation error in `Types.Normalized`. Use [**Normalize**](#normalize) on a copy of the point instead. @@ -79,7 +93,9 @@ Returns the vector scaled to length 1. Syntax: *point*.**Transposed** As **GpPointF** + Read-only. Returns a point with **X** and **Y** exchanged. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() @@ -98,20 +114,26 @@ Debug.Print GpPointF(1, 2).Transposed.X ' 2 Syntax: *point*.**AsGpPoint**( ) As **GpPoint** + Returns the point with its coordinates rounded to whole numbers. Halves go to the even number: 2.5 becomes 2 and 3.5 becomes 4. + ### SetFrom Syntax: *point*.**SetFrom**( *source* ) + Sets both coordinates from a [**GpPoint**](GpPoint). + ### AsPOINT, SetFromPOINT Syntax: *point*.**AsPOINT**( ) As **GDIP_POINT** \ Syntax: *point*.**SetFromPOINT**( *source* As **GDIP_POINT** ) + Convert to and from a structure of two **Long** fields `x` and `y`, which is the layout of the Windows `POINT`. The structure is declared as **GDIP_POINT**. **AsPOINT** rounds as **AsGpPoint** does. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() @@ -128,9 +150,11 @@ Debug.Print winPoint.x & "," & winPoint.y ' 9,2 These change the point they are called on. + > [!NOTE] 0.9.45.0 > **Add** and **Sub** change the point they are called on. Before version 0.9.0.43 they returned the result and left the point alone. Use [**Sum**](#sum-diff) and [**Diff**](#sum-diff) to get a new point. + > [!NOTE] 0.9.45.0, BETA 1005 > The compiler reads `p.Scale(2)` as the start of a `Scale` statement and reports a syntax error. Write `p.Scale 2` or `Call p.Scale(2)`. **Scale2**, **InvScale** and the other methods are not affected. @@ -146,26 +170,34 @@ Adds *other* to the point, or subtracts it. Syntax: *point*.**Scale** *factor* \ Syntax: *point*.**Scale2**( *xFactor*, *yFactor* ) + Multiplies both coordinates by *factor* (**Single**), or **X** by *xFactor* and **Y** by *yFactor*. + ### InvScale, InvScale2 Syntax: *point*.**InvScale**( *divisor* ) \ Syntax: *point*.**InvScale2**( *xDivisor*, *yDivisor* ) + Divides both coordinates by *divisor*, or **X** by *xDivisor* and **Y** by *yDivisor*. + ### Transpose Syntax: *point*.**Transpose** + Exchanges **X** and **Y**. + ### Normalize Syntax: *point*.**Normalize** + Sets the length to 1 and keeps the direction. A zero vector stays as it is. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() @@ -215,13 +247,17 @@ Returns the point with its coordinates divided. Syntax: *point*.**DotProd**( *other* As **GpPointF** ) As **Single** + Returns the dot product, `X * other.X + Y * other.Y`. + ### Equals Syntax: *point*.**Equals**( *other* As **GpPointF** ) As **Boolean** + **True** when both coordinates are exactly equal to those of *other*. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() diff --git a/docs/Reference/Contributed/GdiPlus/Types/GpPropertyItem.md b/docs/Reference/Contributed/GdiPlus/Types/GpPropertyItem.md index e36e7989..bdb9614e 100644 --- a/docs/Reference/Contributed/GdiPlus/Types/GpPropertyItem.md +++ b/docs/Reference/Contributed/GdiPlus/Types/GpPropertyItem.md @@ -52,6 +52,7 @@ Syntax: *v* **=** *item*.**TheLong** A property returns 0 when the type of the value is not the one it reads. + > [!WARNING] 0.9.45.0 > Several of the readers test the wrong type and so return 0 for the values they are meant to read: **TheSByte** works only for a byte (not signed), **TheSLong** only for a short, **TheSShort** only for an unsigned short, and **TheDouble** only for a single. **AsDouble** returns 0 for the signed byte, signed short, signed long and double types for the same reason. **TheSRational** and the other readers work. @@ -63,6 +64,7 @@ Syntax: *item*.**GetSRational**( *num*, *denom* ) Read a rational value as its numerator and denominator without dividing: **GetRational** as unsigned numbers (**LongLong**), **GetSRational** as signed ones (**Long**). + > [!WARNING] 0.9.45.0 > **GetSRational** tests for an unsigned rational, so it reads nothing from a signed one. diff --git a/docs/Reference/Contributed/GdiPlus/Types/GpRect.md b/docs/Reference/Contributed/GdiPlus/Types/GpRect.md index 39fce2de..16c11e15 100644 --- a/docs/Reference/Contributed/GdiPlus/Types/GpRect.md +++ b/docs/Reference/Contributed/GdiPlus/Types/GpRect.md @@ -11,9 +11,14 @@ has_toc: false A rectangle with **Long** coordinates, whose sides are parallel to the axes. + **GpRect** is the rectangle that the integer overloads of the drawing methods take and return. It is 16 bytes: the fields **X**, **Y**, **Width** and **Height**, followed by methods that read and change them. The floating-point counterpart is [**GpRectF**](GpRectF); a point is a [**GpPoint**](GpPoint) and a size a [**GpSize**](GpSize). It mirrors the `Rect` class of the GDI+ C++ wrapper. + + + A variable declared with `Dim` starts as an empty rectangle at (0, 0). + ## Creating @@ -25,6 +30,7 @@ Syntax: **GpRect**( *location*, *size* ) \ Syntax: **GpRect**( *rect* ) \ Syntax: **GpRectFrom**( *rect* ) + The module function **GpRect** returns a new rectangle. The forms without a size give a rectangle of width 0 and height 0. *x*, *y*, *width*, *height* @@ -38,6 +44,7 @@ The module function **GpRect** returns a new rectangle. The forms without a size *rect* : *required* For **GpRect**, a [**GpRectF**](GpRectF), whose values are rounded to whole numbers (halves to the even number), or a structure of four **Long** fields `Left`, `Top`, `Right` and `Bottom`, which is the layout of the Windows `RECT` and is declared as **GDIP_RECT**. For **GpRectFrom**, a structure of four **Single** fields with the same names, declared as **GDIP_RECTF**. The rectangle has the same position, and the width and height are the differences of the edges. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() @@ -55,6 +62,7 @@ Debug.Print e.X & "," & e.Y & "," & e.Width & "," & e.Height ' 1,2,10,20 ## Fields and properties + > [!NOTE] 0.9.45.0 > The location and the size are independent. Setting **Location** does not change the size, and setting **Size** does not change the location. @@ -78,19 +86,25 @@ Returns or sets the width and height. Syntax: *rect*.**Left** As **Long** + Read-only. The coordinates of the four edges: **Left** is **X**, **Top** is **Y**, **Right** is **X** plus **Width** and **Bottom** is **Y** plus **Height**. + ### TopLeft, TopRight, BottomLeft, BottomRight Syntax: *rect*.**TopLeft** As **GpPoint** + Read-only. The corners of the rectangle. + ### IsEmptyArea Syntax: *rect*.**IsEmptyArea** As **Boolean** + Read-only. **True** when the width or the height is 0 or negative. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() @@ -111,33 +125,43 @@ Debug.Print r.IsEmptyArea ' False Syntax: *rect*.**AsGpRectF**( ) As **GpRectF** + Returns the same rectangle with **Single** values. + ### SetFrom Syntax: *rect*.**SetFrom**( *source* As **GpRectF** ) + Sets the rectangle from a [**GpRectF**](GpRectF), rounded as the constructor does. + ### AsRECT, AsRECTF Syntax: *rect*.**AsRECT**( ) As **GDIP_RECT** \ Syntax: *rect*.**AsRECTF**( ) As **GDIP_RECTF** + Return the rectangle as the four edges. + ### SetFromRECT, SetFromRECTF Syntax: *rect*.**SetFromRECT**( *source* As **GDIP_RECT** ) \ Syntax: *rect*.**SetFromRECTF**( *source* As **GDIP_RECTF** ) + Set the position and size from the four edges of *source*. **SetFromRECTF** rounds. + ### ToRECT Syntax: *rect*.**ToRECT**( *destination* As **GDIP_RECT** ) + Writes the four edges into *destination*. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() @@ -162,26 +186,34 @@ These change the rectangle they are called on. Syntax: *rect*.**Offset**( *dx*, *dy* ) \ Syntax: *rect*.**OffsetPt**( *point* As **GpPoint** ) + Moves the rectangle by the given distance. + ### Inflate, InflatePt Syntax: *rect*.**Inflate**( *dx*, *dy* ) \ Syntax: *rect*.**InflatePt**( *point* As **GpPoint** ) + Moves the left and right edges apart by *dx* each and the top and bottom edges apart by *dy* each. **X** and **Y** decrease by *dx* and *dy*, and **Width** and **Height** increase by twice as much. A negative value shrinks the rectangle. **InflatePt** takes *dx* and *dy* from the point. + ### Intersect Syntax: *rect*.**Intersect**( *other* As **GpRect** ) As **Boolean** + Sets the rectangle to its intersection with *other*, and returns **True** when the result is not empty. When the two do not overlap the rectangle is set to one with a negative width or height. + ### Unite Syntax: *rect*.**Unite**( *other* As **GpRect** ) As **Boolean** + Sets the rectangle to the smallest rectangle that holds both it and *other*. Returns **False** when the result is empty, which happens only when both are empty rectangles at the same location: an empty rectangle counts as the single point at its location. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() @@ -211,33 +243,43 @@ These return a new value and leave the rectangle unchanged. Syntax: *rect*.**Equals**( *other* As **GpRect** ) As **Boolean** + **True** when the position and the size equal those of *other*. + ### Contains2, ContainsPt Syntax: *rect*.**Contains2**( *x*, *y* ) As **Boolean** \ Syntax: *rect*.**ContainsPt**( *point* As **GpPoint** ) As **Boolean** + **True** when the point is inside the rectangle. The left and top edges are inside; the right and bottom edges are not. + ### Contains Syntax: *rect*.**Contains**( *other* As **GpRect** ) As **Boolean** + **True** when *other* lies entirely within the rectangle. Sharing an edge counts as within. + ### IntersectsWith Syntax: *rect*.**IntersectsWith**( *other* As **GpRect** ) As **Boolean** + **True** when the two rectangles overlap. Rectangles that only touch at an edge do not overlap. + ### IntersectedWith, UnitedWith Syntax: *rect*.**IntersectedWith**( *other* As **GpRect** ) As **GpRect** \ Syntax: *rect*.**UnitedWith**( *other* As **GpRect** ) As **GpRect** + Return the intersection or the bounding rectangle. As with [**Intersect**](#intersect) and [**Unite**](#unite), the intersection of rectangles that do not overlap has a negative width or height. + ### OffsetBy, OffsetByPt, InflatedBy, InflatedByPt @@ -248,6 +290,7 @@ Syntax: *rect*.**InflatedByPt**( *point* As **GpPoint** ) As **GpRect** Return a copy of the rectangle moved or inflated as [**Offset**](#offset-offsetpt) and [**Inflate**](#inflate-inflatept) do. + > [!NOTE] 0.9.45.0, BETA 1005 > A program that names any of these four methods does not link: the linker reports a code generation error in the method. Copy the rectangle and call **Offset**, **OffsetPt**, **Inflate** or **InflatePt** on the copy. @@ -273,7 +316,9 @@ Debug.Print r.Equals(GpRect(10, 20, 30, 40)) ' True Syntax: **Intersection**( *result* As **GpRect**, *a* As **GpRect**, *b* As **GpRect** ) As **Boolean** \ Syntax: **Union**( *result* As **GpRect**, *a* As **GpRect**, *b* As **GpRect** ) As **Boolean** + Set *result* to the intersection of *a* and *b*, or to the smallest rectangle that holds both. Each returns **True** when the result is not empty. The functions also exist for [**GpRectF**](GpRectF) arguments. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() diff --git a/docs/Reference/Contributed/GdiPlus/Types/GpRectF.md b/docs/Reference/Contributed/GdiPlus/Types/GpRectF.md index 434142ef..84b76aa4 100644 --- a/docs/Reference/Contributed/GdiPlus/Types/GpRectF.md +++ b/docs/Reference/Contributed/GdiPlus/Types/GpRectF.md @@ -11,9 +11,14 @@ has_toc: false A rectangle with **Single** coordinates, whose sides are parallel to the axes. + **GpRectF** is the rectangle that the **Single** overloads of the drawing methods take and return. It is 16 bytes: the fields **X**, **Y**, **Width** and **Height**, followed by methods that read and change them. The integer counterpart is [**GpRect**](GpRect); a point is a [**GpPointF**](GpPointF) and a size a [**GpSizeF**](GpSizeF). It mirrors the `RectF` class of the GDI+ C++ wrapper. + + + A variable declared with `Dim` starts as an empty rectangle at (0, 0). + ## Creating @@ -25,6 +30,7 @@ Syntax: **GpRectF**( *location*, *size* ) \ Syntax: **GpRectF**( *rect* ) \ Syntax: **GpRectFFrom**( *rect* ) + The module function **GpRectF** returns a new rectangle. The forms without a size give a rectangle of width 0 and height 0. *x*, *y*, *width*, *height* @@ -38,7 +44,9 @@ The module function **GpRectF** returns a new rectangle. The forms without a siz *rect* : *required* For **GpRectF**, a [**GpRect**](GpRect) or a structure of four **Single** fields `Left`, `Top`, `Right` and `Bottom`, declared as **GDIP_RECTF**. For **GpRectFFrom**, a structure of four **Long** fields with the same names, which is the layout of the Windows `RECT` and is declared as **GDIP_RECT**. The rectangle has the same position, and the width and height are the differences of the edges. + + > [!WARNING] 0.9.45.0 > **GpRectFFrom** returns an empty rectangle at (0, 0) whatever its argument holds. Use [**SetFromRECT**](#setfromrect-setfromrectf) instead. @@ -58,6 +66,7 @@ Debug.Print e.X & "," & e.Y & "," & e.Width & "," & e.Height ' 1.5,2.5,10,20 ## Fields and properties + > [!NOTE] 0.9.45.0 > The location and the size are independent. Setting **Location** does not change the size, and setting **Size** does not change the location. @@ -81,25 +90,33 @@ Returns or sets the width and height. Syntax: *rect*.**Left** As **Single** + Read-only. The coordinates of the four edges: **Left** is **X**, **Top** is **Y**, **Right** is **X** plus **Width** and **Bottom** is **Y** plus **Height**. + ### TopLeft, TopRight, BottomLeft, BottomRight Syntax: *rect*.**TopLeft** As **GpPointF** + Read-only. The corners of the rectangle. + ### IsEmptyArea Syntax: *rect*.**IsEmptyArea** As **Boolean** + Read-only. **True** when the width or the height is at most [**REAL_EPSILON**](../Helpers#real_epsilon), which includes a negative one. + ### Diagonal Syntax: *rect*.**Diagonal** [ = *value* ] As **Single** + Returns the length of the diagonal. Setting it scales **Width** and **Height** by the same factor so that the diagonal has the given length; the location does not change. A rectangle whose diagonal is shorter than twice [**REAL_EPSILON**](../Helpers#real_epsilon) is not changed. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() @@ -123,33 +140,43 @@ Debug.Print r.IsEmptyArea ' False Syntax: *rect*.**AsGpRect**( ) As **GpRect** + Returns the rectangle with all four values rounded to whole numbers, halves to the even number. + ### SetFrom Syntax: *rect*.**SetFrom**( *source* As **GpRect** ) + Sets the rectangle from a [**GpRect**](GpRect). + ### AsRECT, AsRECTF Syntax: *rect*.**AsRECT**( ) As **GDIP_RECT** \ Syntax: *rect*.**AsRECTF**( ) As **GDIP_RECTF** + Return the rectangle as the four edges. **AsRECT** rounds each edge as **AsGpRect** does. + ### SetFromRECT, SetFromRECTF Syntax: *rect*.**SetFromRECT**( *source* As **GDIP_RECT** ) \ Syntax: *rect*.**SetFromRECTF**( *source* As **GDIP_RECTF** ) + Set the position and size from the four edges of *source*. + ### ToRECTF Syntax: *rect*.**ToRECTF**( *destination* As **GDIP_RECTF** ) + Writes the four edges into *destination*. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() @@ -174,26 +201,34 @@ These change the rectangle they are called on. Syntax: *rect*.**Offset**( *dx*, *dy* ) \ Syntax: *rect*.**OffsetPt**( *point* As **GpPointF** ) + Moves the rectangle by the given distance. + ### Inflate, InflatePt Syntax: *rect*.**Inflate**( *dx*, *dy* ) \ Syntax: *rect*.**InflatePt**( *point* As **GpPointF** ) + Moves the left and right edges apart by *dx* each and the top and bottom edges apart by *dy* each. **X** and **Y** decrease by *dx* and *dy*, and **Width** and **Height** increase by twice as much. A negative value shrinks the rectangle. **InflatePt** takes *dx* and *dy* from the point. + ### Intersect Syntax: *rect*.**Intersect**( *other* As **GpRectF** ) As **Boolean** + Sets the rectangle to its intersection with *other*, and returns **True** when the result is not empty. When the two do not overlap the rectangle is set to one with a negative width or height. + ### Unite Syntax: *rect*.**Unite**( *other* As **GpRectF** ) As **Boolean** + Sets the rectangle to the smallest rectangle that holds both it and *other*. Returns **False** when the result is empty, which happens only when both are empty rectangles at the same location: an empty rectangle counts as the single point at its location. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() @@ -223,33 +258,43 @@ These return a new value and leave the rectangle unchanged. Syntax: *rect*.**Equals**( *other* As **GpRectF** ) As **Boolean** + **True** when the position and the size are exactly those of *other*. + ### Contains2, ContainsPt Syntax: *rect*.**Contains2**( *x*, *y* ) As **Boolean** \ Syntax: *rect*.**ContainsPt**( *point* As **GpPointF** ) As **Boolean** + **True** when the point is inside the rectangle. The left and top edges are inside; the right and bottom edges are not. + ### Contains Syntax: *rect*.**Contains**( *other* As **GpRectF** ) As **Boolean** + **True** when *other* lies entirely within the rectangle. Sharing an edge counts as within. + ### IntersectsWith Syntax: *rect*.**IntersectsWith**( *other* As **GpRectF** ) As **Boolean** + **True** when the two rectangles overlap. Rectangles that only touch at an edge do not overlap. + ### IntersectedWith, UnitedWith Syntax: *rect*.**IntersectedWith**( *other* As **GpRectF** ) As **GpRectF** \ Syntax: *rect*.**UnitedWith**( *other* As **GpRectF** ) As **GpRectF** + Return the intersection or the bounding rectangle. As with [**Intersect**](#intersect) and [**Unite**](#unite), the intersection of rectangles that do not overlap has a negative width or height. + ### OffsetBy, OffsetByPt, InflatedBy, InflatedByPt @@ -260,6 +305,7 @@ Syntax: *rect*.**InflatedByPt**( *point* As **GpPointF** ) As **GpRectF** Return a copy of the rectangle moved or inflated as [**Offset**](#offset-offsetpt) and [**Inflate**](#inflate-inflatept) do. + > [!NOTE] 0.9.45.0, BETA 1005 > A program that names any of these four methods does not link: the linker reports a code generation error in the method. Copy the rectangle and call **Offset**, **OffsetPt**, **Inflate** or **InflatePt** on the copy. @@ -287,7 +333,9 @@ Debug.Print r.Equals(GpRectF(10, 20, 30, 40)) ' True Syntax: **Intersection**( *result* As **GpRectF**, *a* As **GpRectF**, *b* As **GpRectF** ) As **Boolean** \ Syntax: **Union**( *result* As **GpRectF**, *a* As **GpRectF**, *b* As **GpRectF** ) As **Boolean** + Set *result* to the intersection of *a* and *b*, or to the smallest rectangle that holds both. Each returns **True** when the result is not empty. The functions also exist for [**GpRect**](GpRect) arguments. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() diff --git a/docs/Reference/Contributed/GdiPlus/Types/GpSize.md b/docs/Reference/Contributed/GdiPlus/Types/GpSize.md index 54ac6e40..cff755a8 100644 --- a/docs/Reference/Contributed/GdiPlus/Types/GpSize.md +++ b/docs/Reference/Contributed/GdiPlus/Types/GpSize.md @@ -11,9 +11,14 @@ has_toc: false A width and a height as **Long** values. + **GpSize** is the size that the integer overloads of the drawing and measuring methods take and return. It is 8 bytes: the fields **Width** and **Height**, followed by methods that read and change them. The floating-point counterpart is [**GpSizeF**](GpSizeF); a point is a [**GpPoint**](GpPoint) and a rectangle a [**GpRect**](GpRect). It mirrors the `Size` class of the GDI+ C++ wrapper. + + + A variable declared with `Dim` starts at 0 by 0. + ## Creating @@ -22,6 +27,7 @@ Syntax: **GpSize**( *width*, *height* ) \ Syntax: **GpSize**( *size* ) \ Syntax: **GpSizeFrom**( *size* ) + The module function **GpSize** returns a new size. *width*, *height* @@ -31,7 +37,9 @@ The module function **GpSize** returns a new size. : *required* For **GpSize**, a [**GpSizeF**](GpSizeF); its dimensions are rounded to whole numbers, halves to the even number (2.5 becomes 2, 3.5 becomes 4). For **GpSizeFrom**, a structure of two **Long** fields `cx` and `cy`, which is the layout of the Windows `SIZE` and is declared as **GDIP_SIZE**. With no arguments the size is 0 by 0. + + > [!WARNING] 0.9.45.0 > **GpSizeFrom** returns a size of 0 by 0 whatever its argument holds. Set the size with [**SetFromSIZE**](#assize-setfromsize) instead. @@ -54,7 +62,9 @@ The dimensions, as **Long** fields. Assign them directly. Syntax: *size*.**Empty** As **Boolean** + Read-only. **True** when both dimensions are 0. + ## Conversion @@ -62,20 +72,26 @@ Read-only. **True** when both dimensions are 0. Syntax: *size*.**AsGpSizeF**( ) As **GpSizeF** + Returns the same dimensions as **Single** values. + ### SetFrom Syntax: *size*.**SetFrom**( *source* ) + Sets both dimensions from a [**GpSizeF**](GpSizeF), rounded as the constructor does. + ### AsSIZE, SetFromSIZE Syntax: *size*.**AsSIZE**( ) As **GDIP_SIZE** \ Syntax: *size*.**SetFromSIZE**( *source* As **GDIP_SIZE** ) + Convert to and from the `SIZE`-shaped structure described under [Creating](#creating). + ### AsGpRect, AsGpRectF, AsRECT @@ -83,7 +99,9 @@ Syntax: *size*.**AsGpRect**( ) As **GpRect** \ Syntax: *size*.**AsGpRectF**( ) As **GpRectF** \ Syntax: *size*.**AsRECT**( ) As **GDIP_RECT** + Return a rectangle of this size with its top left corner at (0, 0). **AsRECT** returns the structure of four **Long** fields `Left`, `Top`, `Right` and `Bottom`, which is the layout of the Windows `RECT`; it is declared as **GDIP_RECT**. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() @@ -99,6 +117,7 @@ Debug.Print s.AsSIZE().cx & "," & s.AsSIZE().cy ' 9,10 ## Modifying methods + > [!NOTE] 0.9.45.0 > **Add** and **Sub** change the size they are called on. Before version 0.9.0.43 they returned the result and left the size alone. Use [**Sum**](#sum-diff) and [**Diff**](#sum-diff) to get a new size. @@ -122,7 +141,9 @@ Returns the sum of the two sizes, or the size less *other*. Syntax: *size*.**Equals**( *other* As **GpSize** ) As **Boolean** + **True** when both dimensions equal those of *other*. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() diff --git a/docs/Reference/Contributed/GdiPlus/Types/GpSizeF.md b/docs/Reference/Contributed/GdiPlus/Types/GpSizeF.md index 6d53c9d6..adaad87c 100644 --- a/docs/Reference/Contributed/GdiPlus/Types/GpSizeF.md +++ b/docs/Reference/Contributed/GdiPlus/Types/GpSizeF.md @@ -11,9 +11,14 @@ has_toc: false A width and a height as **Single** values. + **GpSizeF** is the size that the **Single** overloads of the drawing and measuring methods take and return. It is 8 bytes: the fields **Width** and **Height**, followed by methods that read and change them. The integer counterpart is [**GpSize**](GpSize); a point is a [**GpPointF**](GpPointF) and a rectangle a [**GpRectF**](GpRectF). It mirrors the `SizeF` class of the GDI+ C++ wrapper. + + + A variable declared with `Dim` starts at 0 by 0. + ## Creating @@ -22,6 +27,7 @@ Syntax: **GpSizeF**( *width*, *height* ) \ Syntax: **GpSizeF**( *size* ) \ Syntax: **GpSizeFFrom**( *size* ) + The module function **GpSizeF** returns a new size. *width*, *height* @@ -31,7 +37,9 @@ The module function **GpSizeF** returns a new size. : *required* For **GpSizeF**, a [**GpSize**](GpSize) with the same dimensions. For **GpSizeFFrom**, a structure of two **Long** fields `cx` and `cy`, which is the layout of the Windows `SIZE` and is declared as **GDIP_SIZE**. With no arguments the size is 0 by 0. + + > [!WARNING] 0.9.45.0 > **GpSizeFFrom** returns a size of 0 by 0 whatever its argument holds. Set the size with [**SetFromSIZE**](#assize-setfromsize) instead. @@ -54,13 +62,17 @@ The dimensions, as **Single** fields. Assign them directly. Syntax: *size*.**Empty** As **Boolean** + Read-only. **True** when both dimensions are exactly 0. + ### Diagonal Syntax: *size*.**Diagonal** [ = *value* ] As **Single** + Returns the length of the diagonal of a rectangle of this size. Setting it scales both dimensions so that the diagonal has the given length. Setting the diagonal of a size whose diagonal is shorter than twice [**REAL_EPSILON**](../Helpers#real_epsilon) changes nothing. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() @@ -76,20 +88,26 @@ Debug.Print s.Width & "," & s.Height ' 6,8 Syntax: *size*.**AsGpSize**( ) As **GpSize** + Returns the size with both dimensions rounded to whole numbers, halves to the even number (2.5 becomes 2, 3.5 becomes 4). + ### SetFrom Syntax: *size*.**SetFrom**( *source* ) + Sets both dimensions from a [**GpSize**](GpSize). + ### AsSIZE, SetFromSIZE Syntax: *size*.**AsSIZE**( ) As **GDIP_SIZE** \ Syntax: *size*.**SetFromSIZE**( *source* As **GDIP_SIZE** ) + Convert to and from the `SIZE`-shaped structure described under [Creating](#creating). **AsSIZE** rounds as **AsGpSize** does. + ### AsGpRectF, AsGpRect, AsRECT @@ -97,7 +115,9 @@ Syntax: *size*.**AsGpRectF**( ) As **GpRectF** \ Syntax: *size*.**AsGpRect**( ) As **GpRect** \ Syntax: *size*.**AsRECT**( ) As **GDIP_RECT** + Return a rectangle of this size with its top left corner at (0, 0). **AsRECT** returns the structure of four **Long** fields `Left`, `Top`, `Right` and `Bottom`, which is the layout of the Windows `RECT`; it is declared as **GDIP_RECT**. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() @@ -117,9 +137,11 @@ Debug.Print s.AsRECT().Right & "," & s.AsRECT().Bottom ' 9,10 These change the size they are called on. + > [!NOTE] 0.9.45.0 > **Add** and **Sub** change the size they are called on. Before version 0.9.0.43 they returned the result and left the size alone. Use [**Sum**](#sum-diff) and [**Diff**](#sum-diff) to get a new size. + > [!NOTE] 0.9.45.0, BETA 1005 > The compiler reads `s.Scale(2)` as the start of a `Scale` statement and reports a syntax error. Write `s.Scale 2` or `Call s.Scale(2)`. **Scale2**, **InvScale** and the other methods are not affected. @@ -135,14 +157,18 @@ Adds *other* to the size, or subtracts it. Syntax: *size*.**Scale** *factor* \ Syntax: *size*.**Scale2**( *widthFactor*, *heightFactor* ) + Multiplies both dimensions by *factor*, or **Width** by *widthFactor* and **Height** by *heightFactor*. + ### InvScale, InvScale2 Syntax: *size*.**InvScale**( *divisor* ) \ Syntax: *size*.**InvScale2**( *widthDivisor*, *heightDivisor* ) + Divides both dimensions by *divisor*, or **Width** by *widthDivisor* and **Height** by *heightDivisor*. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() @@ -187,7 +213,9 @@ Returns the size with its dimensions divided. Syntax: *size*.**Equals**( *other* As **GpSizeF** ) As **Boolean** + **True** when both dimensions are exactly equal to those of *other*. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() diff --git a/docs/Reference/Contributed/GdiPlus/Types/LevelsParams.md b/docs/Reference/Contributed/GdiPlus/Types/LevelsParams.md index eed3ef16..dae5b229 100644 --- a/docs/Reference/Contributed/GdiPlus/Types/LevelsParams.md +++ b/docs/Reference/Contributed/GdiPlus/Types/LevelsParams.md @@ -20,6 +20,7 @@ The type mirrors the GDI+ [LevelsParams](https://learn.microsoft.com/en-us/windo | **midtone** | **Long** | The midtone adjustment, from -100 to 100. 0 changes nothing. | | **shadow** | **Long** | The shadow level, from 0 to 100. 0 leaves the bottom of the range alone. It has to be lower than the highlight level. | + > [!NOTE] 0.9.45.0 > The first field is named **highight**, with the letter l missing, and the field **highlight** does not exist. The argument of the function **LevelsParams** is spelled correctly. diff --git a/docs/Reference/Contributed/GdiPlus/Types/PathPointType.md b/docs/Reference/Contributed/GdiPlus/Types/PathPointType.md index 8415cece..9f25fc7f 100644 --- a/docs/Reference/Contributed/GdiPlus/Types/PathPointType.md +++ b/docs/Reference/Contributed/GdiPlus/Types/PathPointType.md @@ -39,16 +39,20 @@ All the properties are read-only and return a value from the byte. **ReservedFlag1**, **ReservedFlag2** : **Boolean** values of the two reserved flags. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() Dim t As PathPointType t._value = PathPointTypeBezier Or PathPointTypeCloseSubpath Debug.Print CLng(t.PointType) & " " & t.CloseSubpath & " " & t.PathMarker ' 3 True False ``` + + > [!WARNING] BETA 1005 > Reading a property of an element of an array, as in `types(i).CloseSubpath`, stops the program with an access violation. Copy the element to a variable and read the property of that. A type held in a variable or in a field of a **TypedPoint** is read without trouble. + ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() Dim p As GraphicsPath = GraphicsPath() @@ -58,6 +62,7 @@ p.GetTypes types Dim t As PathPointType = types(4) Debug.Print CLng(t.PointType) & " " & t.CloseSubpath ' 1 True ``` + ## See Also diff --git a/docs/Reference/Contributed/GdiPlus/Types/RedEyeCorrectionParams.md b/docs/Reference/Contributed/GdiPlus/Types/RedEyeCorrectionParams.md index 8038cf40..2efd49e7 100644 --- a/docs/Reference/Contributed/GdiPlus/Types/RedEyeCorrectionParams.md +++ b/docs/Reference/Contributed/GdiPlus/Types/RedEyeCorrectionParams.md @@ -63,8 +63,9 @@ Syntax: *params* **= RedEyeCorrectionParams**( *areas*( ) ) *areas* : *required* An array of [**GpRect**](GpRect): the rectangles to copy into newly allocated memory. + > [!WARNING] 0.9.45.0 -> The function stops the program with an access violation when the array has elements. Allocate and fill the structure with **Allocate** and **Area**, as the example does. A [**RedEyeCorrection**](../RedEyeCorrection) effect built from the structure does not work either. +> The function stops the program with an access violation when the array has elements. Allocate and fill the structure with **Allocate** and **Area**, as the example does. ```tb check_build check_run Dim user As GdiPlusUser = GdiPlusUser() diff --git a/docs/Reference/Contributed/GdiPlus/VBGraphics.md b/docs/Reference/Contributed/GdiPlus/VBGraphics.md index b7f5f211..6388ad0c 100644 --- a/docs/Reference/Contributed/GdiPlus/VBGraphics.md +++ b/docs/Reference/Contributed/GdiPlus/VBGraphics.md @@ -14,6 +14,7 @@ A [**Graphics**](Graphics) that also draws with the `Line`, `Circle` and `PSet` The colours differ from Visual Basic's. A colour argument is a [**Color**](Types/Color) value in the layout `&HAARRGGBB`, with its alpha channel, and not a `COLORREF`. + > [!WARNING] 0.9.45.0 > `vbRed` and the result of `RGB` are `COLORREF` values with no alpha: `&H0000FF` has alpha 0, so with the default **UseTransparentAlpha** of **True** a line drawn in it is invisible. Pass `&HFFFF0000` for red, or `Color(255, 0, 0).Value`. With **UseTransparentAlpha** set to **False** a colour with alpha 0 is drawn opaque, but `vbRed` then comes out blue, because the bytes are read as `&HAARRGGBB`. @@ -86,6 +87,7 @@ Syntax: *graphics*.**DrawWidth** [ = *width* ] As **Single** The width of the pen, in the units of the object. Setting the width of the pen changes how the lines of **Line**, **Circle** and **PSet** are drawn. + > [!WARNING] 0.9.45.0 > The pen is created by the first call to **Line**, **Circle** or **PSet**. Until then **DrawWidth** reads 0 and sets **LastResult** to 2, *InvalidParameter*, and an assignment to it is ignored without an error. Draw once before setting **DrawWidth**. @@ -129,6 +131,7 @@ Draws a line, or with **B** a box with those opposite corners, or with **BF** a **B** draws the outline in the colour. When **FillStyle** is solid, the inside is then filled with **FillColor**, over the inner half of the outline. + > [!WARNING] 0.9.45.0 > **BF** fills the wrong rectangle: it starts at the second corner instead of the first, so the box is filled one box-height too low (its top is at *y2*). The outline is drawn where it should be. To fill a box, set **FillStyle** to solid and use **B**, or call **FillRectangle** of **Graphics**. @@ -161,6 +164,7 @@ Syntax: *graphics*.**Circle** [ **Step** ] **(** *x*, *y* **)**, *radius* [, *co Draws a circle, or an ellipse, with its centre at the point. The outline is in *color*, or **ForeColor**, and the inside is filled with **FillColor** when **FillStyle** is solid. *aspect* is the ratio of the vertical to the horizontal size: below 1 the radius is the horizontal one and the ellipse is wider than tall, above 1 the radius is the vertical one. + > [!WARNING] 0.9.45.0 > *start* and *end*, which draw an arc or a pie, are not implemented. A call that gives either draws nothing and sets **LastResult** to 6, *NotImplemented*. @@ -189,6 +193,7 @@ Syntax: *graphics*.**PSet** [ **Step** ] **(** *x*, *y* **)** [, *color* ] Draws a point in *color*, or **ForeColor**, and makes it the current position. + > [!WARNING] 0.9.45.0 > The point is a line one unit long: **PSet** colours the pixel at (*x*, *y*) and the pixel below it. To set a single pixel, assign to the **Pixel** property of the **Bitmap**. diff --git a/docs/Reference/Contributed/GdiPlus/index.md b/docs/Reference/Contributed/GdiPlus/index.md index ae95a8e3..369d96fa 100644 --- a/docs/Reference/Contributed/GdiPlus/index.md +++ b/docs/Reference/Contributed/GdiPlus/index.md @@ -119,16 +119,19 @@ Debug.Print Hex$(bmp.Pixel(50, 50).argb) ' FFFFFFFF ``` + > [!WARNING] 0.9.45.0 > Two whole numbers without a format do not create a bitmap of that size. `Bitmap(64, 64)` binds to the form that takes a GDI bitmap handle and a palette handle, treats 64 and 64 as handles, and returns an unusable object with **LastResult** 7 (**Win32Error**), without any error being raised. Pass **Single** values, as the sample does: `Bitmap(64!, 64!)`, or give a format, as in `Bitmap(64, 64, PixelFormat32bppARGB)`. [**Bitmap**](Bitmap#creating) has the details. + > [!WARNING] 0.9.45.0 > The package compiles, and a program that uses its common calls with **Color** values works. A number of members give wrong results, fail or never return, and in most cases raise no error. The main ones: > > - A colour passed as a **Long** (the *rgb* forms of [**SolidBrush**](SolidBrush), [**Pen**](Pen), [**Graphics.Clear**](Graphics#clear), [**HatchBrush**](HatchBrush) and the gradient brushes) is read as `&H00RRGGBB`, not as a Visual Basic colour: `SolidBrush(vbRed)` paints blue, and the alpha is dropped. A **CColor** member passed there loses its alpha. Pass a [**Color**](Types/Color). See [Colours](IBrush#colours) and [**Color**](Types/Color#windows-colours). > - [**Image.Save**](Image#save) and **Bitmap.Save** cannot pass encoder parameters: the call returns an error, ignores them, or stops the program. > - [**InstalledFontCollection**](InstalledFontCollection#creating) and [**PrivateFontCollection**](PrivateFontCollection#creating) cannot be created, so no font collection can be used. -> - Some calls never return: the function form of [**HatchBrush**](HatchBrush#creating), the array forms of [**SetBlend**](LinearGradientBrush#setblend) and [**SetInterpolationColors**](LinearGradientBrush#setinterpolationcolors) of the gradient brushes, the stroke cap setters of [**CustomLineCap**](CustomLineCap#setstrokecaps) and of [**ArrowCap**](ArrowCap), and three forms of [**Graphics.DrawImage**](Graphics#drawimage). +> - Some calls never return: the function form of [**HatchBrush**](HatchBrush#creating), the array forms of [**SetBlend**](LinearGradientBrush#setblend) and [**SetInterpolationColors**](LinearGradientBrush#setinterpolationcolors) of the gradient brushes, the stroke cap setters of [**CustomLineCap**](CustomLineCap#setstrokecaps) and of [**ArrowCap**](ArrowCap). +> - Some calls end the program: [**Graphics.DrawImage**](Graphics#drawimage) with a destination rectangle (in a 32-bit program) and with an array of points and a source part, [**Graphics.IsVisible**](Graphics#isvisible) with a **GpPointF** (in a 32-bit program), the methods **TestControlForceBilinear** and **TestControlNoICM** of [**GdiPlusUser**](GdiPlusUser#methods), and drawing an image that was given a [**GdiPlusAbort**](GdiPlusAbort). > - The [**Metafile**](Metafile#creating) constructors that take a file name, and the ones that record, raise error 13. > - The [effects](Effect#parameters) return zeros from **Parameters** unless the size of the structure is passed, and the constructors that take a parameters structure fail or stop the program for seven of the eleven effect classes. > - Reading **Transform** from a [**Graphics**](Graphics#transform), a [**Pen**](Pen#transform) or a brush returns **Nothing**. A **Graphics** and the brushes have [**GetTransform**](ITransformable#gettransform) to read the transformation, and a **Pen** has no way to. @@ -143,11 +146,13 @@ A **GdiPlusUser** must exist while any GDI+ object is used. It starts GDI+, and Dim user As GdiPlusUser = GdiPlusUser() ``` + > [!WARNING] 0.9.45.0 > A **GdiPlusUser** declared `Dim user As New GdiPlusUser` is not created until the variable is first used, so the GDI+ objects made before that fail with status 18. Use `Dim user As GdiPlusUser = GdiPlusUser()`, or `Set` the variable in the load handler. See [**GdiPlusUser**](GdiPlusUser). Every class is created in two ways that are one thing: `New Pen(...)` and the function `Pen(...)`. This section documents both under *Creating* on the page of the class. The structures of the package, such as [**GpPointF**](Types/GpPointF) and [**GpRect**](Types/GpRect), have such functions too: `GpRect(0, 0, 10, 10)`. + > [!NOTE] 0.9.45.0 > A project that has the VB and OLE Automation packages, which every default project has, has a control class **Image** and an OLE type **Font**. In a declaration, write [`GdiPlus.Image`](Image) and [`GdiPlus.Font`](Font); the constructor functions `Image(...)` and `Font(...)` need no prefix. @@ -275,3 +280,7 @@ The [Enumerations](Enumerations/) page lists the 74 enumerations of the package, - [Graphics](Graphics) class -- the drawing surface - [Bitmap](Bitmap) class -- a picture to draw on - [Experimental Packages](../../Experimental/) -- the packages from TWINSERV that are not yet ready for use in a program + +## Test cases + +The notes and warnings of this section are checked by the test cases in [GdiPlus-tests.twinproj](GdiPlus-tests.twinproj), a twinBASIC project. It uses GdiPlus as a [linked package](../../../../Features/Packages/Linked#manual-management): put the package's `.twinpack` in `%APPDATA%\twinBASIC\packages`, then run the project, and each case prints whether it passes. diff --git a/scripts/check_package_tests.mjs b/scripts/check_package_tests.mjs index aee101f1..fec01879 100644 --- a/scripts/check_package_tests.mjs +++ b/scripts/check_package_tests.mjs @@ -93,7 +93,7 @@ if ( // The contributed sections that have no test project yet. Each package's project removes it // from here; the gate fails on a name here with a project, and on a section neither here nor // with one. -const PENDING = new Set(["ArrayListLib", "GdiPlus", "WinReg"]); +const PENDING = new Set(["ArrayListLib", "WinReg"]); const DOCS = path.join(REPO_ROOT, "docs"); const SECTIONS = path.join(DOCS, "Reference", "Contributed"); diff --git a/test/package-tests/GdiPlus/Sources/Br_BrushColourTests.twin b/test/package-tests/GdiPlus/Sources/Br_BrushColourTests.twin new file mode 100644 index 00000000..733ad9a5 --- /dev/null +++ b/test/package-tests/GdiPlus/Sources/Br_BrushColourTests.twin @@ -0,0 +1,201 @@ +' The Long (rgb) forms of the brushes and of Pen: a VB colour handed to them (vbRed, RGB(...), a system colour). +[TestFixture] +Module Br_BrushColourTests + + Private Declare PtrSafe Function GetSysColor Lib "user32" (ByVal nIndex As Long) As Long + + ' &HAARRGGBB of an opaque COLORREF (&H00BBGGRR). + Private Function ArgbOfColorref(ByVal Colorref As Long) As Long + Return &HFF000000 Or ((Colorref And &HFF&) * &H10000) Or (Colorref And &HFF00&) Or ((Colorref And &HFF0000) \ &H10000) + End Function + + ' The pixel a brush paints over opaque white. + Private Function Painted(ByVal Fill As Brush) As Long + Dim bmp As Bitmap = Br_White(4, 4) + Dim g As Graphics = Graphics(bmp) + g.FillRectangle Fill, 0, 0, 4, 4 + Return Br_Px(bmp, 1, 1) + End Function + + [TestCase] + Public Sub SolidBrush_VBColour_PaintsThatColour() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Expect.AreEqual "FFFF0000", Br_Hex(Painted(SolidBrush(vbRed))), "pixel painted by SolidBrush(vbRed)" + Expect.AreEqual "FF0000FF", Br_Hex(Painted(SolidBrush(vbBlue))), "pixel painted by SolidBrush(vbBlue)" + Expect.AreEqual "FFFF0000", Br_Hex(Painted(SolidBrush(RGB(255, 0, 0)))), "pixel painted by SolidBrush(RGB(255, 0, 0))" + Expect.AreEqual "FF0A141E", Br_Hex(Painted(SolidBrush(RGB(10, 20, 30)))), "pixel painted by SolidBrush(RGB(10, 20, 30))" + Expect.Done + End Sub + + [TestCase] + Public Sub SolidBrush_SystemColour_IsTranslated() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Const COLOR_BTNFACE As Long = 15 + Dim face As Long = ArgbOfColorref(GetSysColor(COLOR_BTNFACE)) + Expect.AreEqual Br_Hex(face), Br_Hex(Painted(SolidBrush(vbButtonFace))), "pixel painted by SolidBrush(vbButtonFace), the button face colour" + Expect.Done + End Sub + + [TestCase] + Public Sub SolidBrush_RGBForm_IsOpaque() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim brush As SolidBrush = SolidBrush(&H80112233) + Dim c As Color = brush.Color + Expect.AreEqual 255, CLng(c.A), "alpha of SolidBrush(&H80112233), a Long with a non-zero top byte" + Expect.Done + End Sub + + [TestCase] + Public Sub SolidBrush_CColorTransparent_PaintsNothing() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = Bitmap(4!, 4!) + Dim g As Graphics = Graphics(bmp) + g.Clear Color(255, 0, 0, 255) + g.FillRectangle SolidBrush(CColor.Transparent), 0, 0, 4, 4 + Expect.AreEqual "FF0000FF", Br_Hex(Br_Px(bmp, 1, 1)), "pixel of a blue bitmap after filling it with SolidBrush(CColor.Transparent)" + g.Clear Color(255, 0, 0, 255) + g.FillRectangle SolidBrush(Color(CColor.Transparent)), 0, 0, 4, 4 + Expect.AreEqual "FF0000FF", Br_Hex(Br_Px(bmp, 1, 1)), "pixel of a blue bitmap after filling it with SolidBrush(Color(CColor.Transparent))" + Expect.Done + End Sub + + [TestCase] + Public Sub SolidBrush_RGBProperty_Let_TakesAVBColour() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim brush As SolidBrush = SolidBrush(Color(255, 0, 0, 0)) + brush.RGB = vbRed + Dim c As Color = brush.Color + Expect.AreEqual "FFFF0000", Br_Hex(c.argb), "Color of a SolidBrush after RGB = vbRed" + Expect.Done + End Sub + + [TestCase] + Public Sub SolidBrush_RGBProperty_Get_ReturnsAVBColour() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim brush As SolidBrush = SolidBrush(Color(255, 255, 0, 0)) + Expect.AreEqual vbRed, brush.RGB, "RGB of a SolidBrush whose Color is opaque red, as a VB colour" + Expect.Done + End Sub + + [TestCase] + Public Sub HatchBrush_RGBForm_VBColour_UsesThatColour() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim brush As HatchBrush = New HatchBrush(HatchStyleCross, vbRed, vbBlue) + Dim fore As Color = brush.ForegroundColor + Dim back As Color = brush.BackgroundColor + Expect.AreEqual "FFFF0000", Br_Hex(fore.argb), "ForegroundColor of HatchBrush(HatchStyleCross, vbRed, vbBlue)" + Expect.AreEqual "FF0000FF", Br_Hex(back.argb), "BackgroundColor of HatchBrush(HatchStyleCross, vbRed, vbBlue)" + Expect.Done + End Sub + + [TestCase] + Public Sub LinearGradientBrush_RGBForm_VBColour_UsesThatColour() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim brush As LinearGradientBrush = New LinearGradientBrush(GpRectF(0, 0, 10, 4), vbRed, vbBlue, LinearGradientModeHorizontal) + Dim colors() As Color = brush.LinearColors + Expect.AreEqual "FFFF0000", Br_Hex(colors(1).argb), "start colour of a gradient made with vbRed, vbBlue" + Expect.AreEqual "FF0000FF", Br_Hex(colors(2).argb), "end colour of a gradient made with vbRed, vbBlue" + Expect.Done + End Sub + + [TestCase] + Public Sub LinearGradientBrush_RGBForm_Points_VBColour_UsesThatColour() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim brush As LinearGradientBrush = New LinearGradientBrush(GpPointF(0, 0), GpPointF(10, 0), vbRed, vbBlue) + Dim colors() As Color = brush.LinearColors + Expect.AreEqual "FFFF0000", Br_Hex(colors(1).argb), "start colour of a two-point gradient made with vbRed, vbBlue" + Expect.AreEqual "FF0000FF", Br_Hex(colors(2).argb), "end colour of a two-point gradient made with vbRed, vbBlue" + Expect.Done + End Sub + + [TestCase] + Public Sub LinearGradientBrush_SetLinearColors_RGBForm_VBColour_UsesThatColour() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim brush As LinearGradientBrush = New LinearGradientBrush(GpRectF(0, 0, 10, 4), Color(255, 0, 0, 0), Color(255, 255, 255, 255), LinearGradientModeHorizontal) + brush.SetLinearColors vbRed, vbBlue + Dim colors() As Color = brush.LinearColors + Expect.AreEqual "FFFF0000", Br_Hex(colors(1).argb), "start colour after SetLinearColors vbRed, vbBlue" + Expect.AreEqual "FF0000FF", Br_Hex(colors(2).argb), "end colour after SetLinearColors vbRed, vbBlue" + Expect.Done + End Sub + + [TestCase] + Public Sub PathGradientBrush_CenterRGB_VBColour_UsesThatColour() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim corners() As GpPointF + Br_Corners corners, 10 + Dim brush As PathGradientBrush = New PathGradientBrush(corners) + brush.CenterRGB = vbRed + Dim c As Color = brush.CenterColor + Expect.AreEqual "FFFF0000", Br_Hex(c.argb), "CenterColor after CenterRGB = vbRed" + Expect.Done + End Sub + + [TestCase] + Public Sub Pen_RGBForm_VBColour_UsesThatColour() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim linePen As Pen = Pen(vbRed, 2) + Dim c As Color = linePen.Color + Expect.AreEqual "FFFF0000", Br_Hex(c.argb), "Color of Pen(vbRed, 2)" + Dim rgbPen As Pen = Pen(RGB(0, 0, 255)) + c = rgbPen.Color + Expect.AreEqual "FF0000FF", Br_Hex(c.argb), "Color of Pen(RGB(0, 0, 255))" + Expect.Done + End Sub + + [TestCase] + Public Sub Pen_RGBForm_VBColour_DrawsThatColour() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = Br_White(8, 8) + Dim g As Graphics = Graphics(bmp) + g.DrawLine Pen(vbRed, 2), 0, 4, 8, 4 + Expect.AreEqual "FFFF0000", Br_Hex(Br_Px(bmp, 4, 4)), "pixel on a line drawn with Pen(vbRed, 2)" + Expect.Done + End Sub + + [TestCase] + Public Sub Pen_RGBProperty_Let_TakesAVBColour() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim linePen As Pen = Pen(Color(255, 0, 0, 0)) + linePen.RGB = vbRed + Dim c As Color = linePen.Color + Expect.AreEqual "FFFF0000", Br_Hex(c.argb), "Color of a Pen after RGB = vbRed" + Expect.Done + End Sub + + [TestCase] + Public Sub Pen_RGBProperty_Get_ReturnsAVBColour() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim linePen As Pen = Pen(Color(255, 255, 0, 0)) + Expect.AreEqual vbRed, linePen.RGB, "RGB of a Pen whose Color is opaque red, as a VB colour" + Expect.Done + End Sub + + [TestCase] + Public Sub Pen_Set_RGBForm_VBColour_UsesThatColour() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim linePen As Pen = Pen(Color(255, 0, 0, 0)) + linePen.Set vbRed, 3 + Dim c As Color = linePen.Color + Expect.AreEqual "FFFF0000", Br_Hex(c.argb), "Color of a Pen after Set vbRed, 3" + Expect.AreEqual 3!, linePen.Width, "Width of a Pen after Set vbRed, 3" + Expect.Done + End Sub + +End Module diff --git a/test/package-tests/GdiPlus/Sources/Br_BrushTests.twin b/test/package-tests/GdiPlus/Sources/Br_BrushTests.twin new file mode 100644 index 00000000..3f0586d0 --- /dev/null +++ b/test/package-tests/GdiPlus/Sources/Br_BrushTests.twin @@ -0,0 +1,312 @@ +' IBrush, SolidBrush, HatchBrush and TextureBrush: what the brushes share, and their constructors and clones. +[TestFixture] +Module Br_BrushTests + + ' A gradient brush that has recorded a failure: WrapModeClamp is refused. + Private Function FailedGradient() As LinearGradientBrush + Dim brush As LinearGradientBrush = New LinearGradientBrush(GpRectF(0, 0, 10, 4), Color(255, 0, 0, 0), Color(255, 255, 255, 255), LinearGradientModeHorizontal) + brush.WrapMode = WrapModeClamp + Return brush + End Function + + Private Function Tile() As Bitmap + Dim t As Bitmap = Bitmap(2!, 2!) + t.Pixel(0, 0) = Color(255, 255, 0, 0) + t.Pixel(1, 0) = Color(255, 0, 255, 0) + t.Pixel(0, 1) = Color(255, 0, 0, 255) + t.Pixel(1, 1) = Color(255, 255, 255, 255) + Return t + End Function + + [TestCase] + Public Sub LastResult_StaysUntilClearResult() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim brush As LinearGradientBrush = FailedGradient() + Expect.AreEqual CLng(InvalidParameter), CLng(brush.LastResult), "LastResult after WrapMode = WrapModeClamp" + Dim n As Long = brush.BlendCount + Expect.AreEqual CLng(InvalidParameter), CLng(brush.LastResult), "LastResult after a call that worked, following the failure" + brush.ClearResult + Expect.AreEqual CLng(Ok), CLng(brush.LastResult), "LastResult after ClearResult" + Expect.Done + End Sub + + [TestCase] + Public Sub Clone_AfterEarlierFailure_ReturnsTheCopy() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim brush As LinearGradientBrush = FailedGradient() + Dim copy As LinearGradientBrush = brush.Clone() + Expect.IsFalse copy Is Nothing, "Clone of a brush that recorded an earlier failure returns a copy" + Expect.Done + End Sub + + [TestCase] + Public Sub Clone_AfterClearResult_ReturnsTheCopy() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim brush As LinearGradientBrush = FailedGradient() + brush.ClearResult + Dim copy As LinearGradientBrush = brush.Clone() + Expect.IsFalse copy Is Nothing, "Clone after ClearResult returns a copy" + Expect.Done + End Sub + + [TestCase] + Public Sub TextureBrush_Image_AfterEarlierFailure_ReturnsTheImage() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim tileImage As Bitmap = Tile() + Dim brush As TextureBrush = New TextureBrush(tileImage) + Dim m As Matrix = brush.Transform + Expect.AreEqual CLng(InvalidParameter), CLng(brush.LastResult), "LastResult after reading Transform" + Dim whole As GdiPlus.Image = brush.Image + Expect.IsFalse whole Is Nothing, "Image of a brush that recorded an earlier failure returns the image" + Expect.Done + End Sub + + [TestCase] + Public Sub Type_PathGradientBrush_ReturnsPathGradient() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim corners() As GpPointF + Br_Corners corners, 10 + Dim brush As PathGradientBrush = New PathGradientBrush(corners) + Expect.AreEqual CLng(BrushTypePathGradient), CLng(brush.Type), "Type of a PathGradientBrush" + Expect.Done + End Sub + + [TestCase] + Public Sub Type_EachBrushClass_ReturnsItsKind() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim solid As SolidBrush = SolidBrush(Color(255, 0, 128, 0)) + Dim hatch As HatchBrush = New HatchBrush(HatchStyleCross, Color(255, 255, 0, 0), Color(255, 255, 255, 255)) + Dim linear As LinearGradientBrush = New LinearGradientBrush(GpPointF(0, 0), GpPointF(10, 0), Color(255, 0, 0, 0), Color(255, 255, 255, 255)) + Dim tileImage As Bitmap = Tile() + Dim texture As TextureBrush = New TextureBrush(tileImage) + Expect.AreEqual CLng(BrushTypeSolidColor), CLng(solid.Type), "Type of a SolidBrush" + Expect.AreEqual CLng(BrushTypeHatchFill), CLng(hatch.Type), "Type of a HatchBrush" + Expect.AreEqual CLng(BrushTypeLinearGradient), CLng(linear.Type), "Type of a LinearGradientBrush" + Expect.AreEqual CLng(BrushTypeTextureFill), CLng(texture.Type), "Type of a TextureBrush" + Expect.Done + End Sub + + [TestCase] + Public Sub CloneBrush_KeepsTheClassAndTheSettings() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim original As Brush = New HatchBrush(HatchStyleCross, Color(255, 255, 0, 0), Color(255, 0, 0, 255)) + Dim copy As Brush = original.CloneBrush() + Expect.IsFalse copy Is Nothing, "CloneBrush returns a brush" + Expect.AreEqual CLng(BrushTypeHatchFill), CLng(copy.Type), "Type of the copy of a HatchBrush" + Dim hatch As HatchBrush = copy + Expect.AreEqual CLng(HatchStyleCross), CLng(hatch.HatchStyle), "HatchStyle of the copy" + Dim fore As Color = hatch.ForegroundColor + Expect.AreEqual "FFFF0000", Br_Hex(fore.argb), "ForegroundColor of the copy" + Expect.Done + End Sub + + [TestCase] + Public Sub SolidBrush_Clone_IsIndependent() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim brush As SolidBrush = SolidBrush(Color(128, 255, 0, 0)) + Dim copy As SolidBrush = brush.Clone() + copy.Color = Color(255, 0, 0, 255) + Dim a As Color = brush.Color + Dim b As Color = copy.Color + Expect.AreEqual "80FF0000", Br_Hex(a.argb), "colour of the original after the copy was changed" + Expect.AreEqual "FF0000FF", Br_Hex(b.argb), "colour of the copy" + Expect.Done + End Sub + + [TestCase] + Public Sub HatchBrush_Function_ColorForm_Returns() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim brush As HatchBrush = HatchBrush(HatchStyleCross, Color(255, 255, 0, 0), Color(255, 0, 0, 255)) + Expect.IsFalse brush Is Nothing, "HatchBrush(style, Color, Color) returns a brush" + Expect.Done + End Sub + + [TestCase] + Public Sub HatchBrush_Function_RGBForm_Returns() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim brush As HatchBrush = HatchBrush(HatchStyleCross, &HFF0000, &HFF) + Expect.IsFalse brush Is Nothing, "HatchBrush(style, Long, Long) returns a brush" + Expect.Done + End Sub + + [TestCase] + Public Sub HatchBrush_New_PaintsTheTile() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim red As Color = Color(255, 255, 0, 0) + Dim blue As Color = Color(255, 0, 0, 255) + Dim brush As HatchBrush = New HatchBrush(HatchStyleHorizontal, red, blue) + Dim bmp As Bitmap = Bitmap(8!, 8!) + Dim g As Graphics = Graphics(bmp) + g.FillRectangle brush, 0, 0, 8, 8 + Dim y As Long, row As String + For y = 0 To 3 + If Br_Px(bmp, 3, y) = red.argb Then row = row & "F" Else row = row & "B" + Next + Expect.AreEqual "FBBB", row, "foreground (F) and background (B) pixels of the first four rows of a HatchStyleHorizontal fill" + Expect.Done + End Sub + + [TestCase] + Public Sub TextureBrushI_UsesTheLongRectangle() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim image As Bitmap = Bitmap(4!, 4!) + Dim brush As TextureBrush = TextureBrushI(image, WrapModeTile, 0&, 0&, 2&, 2&) + Expect.AreEqual CLng(Ok), CLng(brush.LastResult), "LastResult of TextureBrushI" + Dim whole As GdiPlus.Image = brush.Image + Expect.AreEqual 2, CLng(whole.Width), "width of the tile of TextureBrushI(image, WrapModeTile, 0, 0, 2, 2)" + Expect.AreEqual 2, CLng(whole.Height), "height of the tile" + Expect.Done + End Sub + + [TestCase] + Public Sub TextureBrush_NewWithSixNumbers_IsTheSingleForm() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim image As Bitmap = Bitmap(4!, 4!) + Dim brush As TextureBrush = New TextureBrush(image, WrapModeTile, 0, 0, 3, 3) + Expect.AreEqual CLng(Ok), CLng(brush.LastResult), "LastResult of New TextureBrush with six numbers" + Dim whole As GdiPlus.Image = brush.Image + Expect.AreEqual 3, CLng(whole.Width), "width of the tile of New TextureBrush(image, WrapModeTile, 0, 0, 3, 3)" + Expect.Done + End Sub + + ' The pixels of the first rows of a bitmap as letters: R, G, B and W for the four colours of the tile, . for anything else. + Private Function TileRow(ByVal Bmp As Bitmap, ByVal Y As Long, ByVal Width As Long) As String + Dim x As Long, a As Long, s As String + For x = 0 To Width - 1 + a = Br_Px(Bmp, x, Y) + If a = &HFFFF0000 Then + s = s & "R" + ElseIf a = &HFF00FF00 Then + s = s & "G" + ElseIf a = &HFF0000FF Then + s = s & "B" + ElseIf a = &HFFFFFFFF Then + s = s & "W" + Else + s = s & "." + End If + Next + Return s + End Function + + [TestCase] + Public Sub TextureBrush_Tile_RepeatsFromTheOrigin() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim tileImage As Bitmap = Tile() + Dim brush As TextureBrush = New TextureBrush(tileImage) + Dim bmp As Bitmap = Bitmap(6!, 4!) + Dim g As Graphics = Graphics(bmp) + g.FillRectangle brush, 0, 0, 6, 4 + Expect.AreEqual "RGRGRG", TileRow(bmp, 0, 6), "row 0 of a bitmap filled with a 2 by 2 tile" + Expect.AreEqual "BWBWBW", TileRow(bmp, 1, 6), "row 1" + Expect.AreEqual CLng(Ok), CLng(brush.LastResult), "LastResult" + Expect.AreEqual CLng(BrushTypeTextureFill), CLng(brush.Type), "Type" + Expect.AreEqual CLng(WrapModeTile), CLng(brush.WrapMode), "default WrapMode" + Expect.Done + End Sub + + [TestCase] + Public Sub TextureBrush_Rectangle_UsesThePartOfTheImage() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim image As Bitmap = Bitmap(4!, 4!) + Dim x As Long, y As Long + For y = 0 To 3 + For x = 0 To 3 + image.Pixel(x, y) = Color(255, x * 60, y * 60, 0) + Next + Next + Dim brush As TextureBrush = New TextureBrush(image, WrapModeTile, GpRectF(2, 2, 2, 2)) + Dim bmp As Bitmap = Bitmap(4!, 2!) + Dim g As Graphics = Graphics(bmp) + g.FillRectangle brush, 0, 0, 4, 2 + Expect.AreEqual "FF787800", Br_Hex(Br_Px(bmp, 0, 0)), "pixel (0, 0) of a fill with the lower right 2 by 2 part of the image" + Expect.AreEqual "FFB47800", Br_Hex(Br_Px(bmp, 1, 0)), "pixel (1, 0)" + Expect.AreEqual "FF787800", Br_Hex(Br_Px(bmp, 2, 0)), "pixel (2, 0)" + Dim whole As GdiPlus.Image = brush.Image + Expect.AreEqual 2, CLng(whole.Width), "width of the tile Image returns" + Expect.AreEqual 2, CLng(whole.Height), "height of the tile Image returns" + Expect.Done + End Sub + + [TestCase] + Public Sub TextureBrush_WrapMode_FlipXMirrorsAndClampDrawsOnce() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim tileImage As Bitmap = Tile() + Dim bmp As Bitmap = Bitmap(6!, 2!) + Dim g As Graphics = Graphics(bmp) + Dim flip As TextureBrush = New TextureBrush(tileImage, WrapModeTileFlipX) + g.Clear Color(255, 0, 0, 0) + g.FillRectangle flip, 0, 0, 6, 2 + Expect.AreEqual "RGGRRG", TileRow(bmp, 0, 6), "row 0 with WrapModeTileFlipX" + Expect.AreEqual "BWWBBW", TileRow(bmp, 1, 6), "row 1 with WrapModeTileFlipX" + Dim clamp As TextureBrush = New TextureBrush(tileImage, WrapModeClamp) + g.Clear Color(255, 0, 0, 0) + g.FillRectangle clamp, 0, 0, 6, 2 + Expect.AreEqual "RG....", TileRow(bmp, 0, 6), "row 0 with WrapModeClamp" + Expect.AreEqual "BW....", TileRow(bmp, 1, 6), "row 1 with WrapModeClamp" + Expect.Done + End Sub + + [TestCase] + Public Sub TextureBrush_TranslateTransform_MovesTheTiles() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim tileImage As Bitmap = Tile() + Dim brush As TextureBrush = New TextureBrush(tileImage) + Expect.AreEqual CLng(Ok), CLng(brush.TranslateTransform(1, 0)), "status of TranslateTransform" + Dim bmp As Bitmap = Bitmap(6!, 2!) + Dim g As Graphics = Graphics(bmp) + g.FillRectangle brush, 0, 0, 6, 2 + Expect.AreEqual "GRGRGR", TileRow(bmp, 0, 6), "row 0 after TranslateTransform 1, 0" + Expect.AreEqual "WBWBWB", TileRow(bmp, 1, 6), "row 1 after TranslateTransform 1, 0" + Expect.Done + End Sub + + [TestCase] + Public Sub TextureBrush_Transform_Get_ReturnsTheMatrix() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim tileImage As Bitmap = Tile() + Dim brush As TextureBrush = New TextureBrush(tileImage) + brush.TranslateTransform 1, 2 + Dim m As Matrix = brush.Transform + Expect.IsFalse m Is Nothing, "Transform returns a matrix" + If Not m Is Nothing Then + Expect.AreEqual 1!, m.OffsetX, "OffsetX of Transform after TranslateTransform 1, 2" + Expect.AreEqual 2!, m.OffsetY, "OffsetY of Transform after TranslateTransform 1, 2" + End If + Expect.AreEqual CLng(Ok), CLng(brush.LastResult), "LastResult after reading Transform" + Expect.Done + End Sub + + [TestCase] + Public Sub TextureBrush_GetTransform_CopiesTheMatrix() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim tileImage As Bitmap = Tile() + Dim brush As TextureBrush = New TextureBrush(tileImage) + brush.TranslateTransform 1, 2 + Dim m As Matrix = New Matrix() + Expect.AreEqual CLng(Ok), CLng(brush.GetTransform(m)), "status of GetTransform" + Expect.AreEqual 1!, m.OffsetX, "OffsetX from GetTransform after TranslateTransform 1, 2" + Expect.AreEqual 2!, m.OffsetY, "OffsetY from GetTransform after TranslateTransform 1, 2" + Expect.Done + End Sub + +End Module diff --git a/test/package-tests/GdiPlus/Sources/Br_CapTests.twin b/test/package-tests/GdiPlus/Sources/Br_CapTests.twin new file mode 100644 index 00000000..9f8f6918 --- /dev/null +++ b/test/package-tests/GdiPlus/Sources/Br_CapTests.twin @@ -0,0 +1,258 @@ +' CustomLineCap and ArrowCap: construction, properties, stroke caps, and the cap as drawn. +[TestFixture] +Module Br_CapTests + + Private Function SmallCap(Optional ByVal BaseCap As GpLineCap = LineCapFlat, Optional ByVal BaseInset As Long = 0) As CustomLineCap + Dim fill As GraphicsPath = New GraphicsPath() + fill.AddRectangle -1, -1, 2, 1 + Dim noStroke As GraphicsPath + Return New CustomLineCap(fill, noStroke, BaseCap, BaseInset) + End Function + + ' ---- CustomLineCap + + [TestCase] + Public Sub CustomLineCap_New_BaseInset_KeepsTheFraction() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim cap As CustomLineCap = SmallCap(LineCapFlat, 1.5) + Expect.AreEqual 1.5!, cap.BaseInset, "BaseInset of a cap made with the base inset 1.5" + Expect.Done + End Sub + + [TestCase] + Public Sub CustomLineCap_BaseInset_Property_TakesASingle() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim cap As CustomLineCap = SmallCap() + cap.BaseInset = 0.5 + Expect.AreEqual 0.5!, cap.BaseInset, "BaseInset after it was set to 0.5" + Expect.Done + End Sub + + [TestCase] + Public Sub CustomLineCap_Properties_RoundTrip() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim cap As CustomLineCap = SmallCap(LineCapRound, 1) + Expect.AreEqual CLng(LineCapRound), CLng(cap.BaseCap), "BaseCap of a cap made with LineCapRound" + Expect.AreEqual 1!, cap.BaseInset, "BaseInset of a cap made with the base inset 1" + Expect.AreEqual 1!, cap.WidthScale, "default WidthScale" + Expect.AreEqual CLng(LineJoinMiter), CLng(cap.StrokeJoin), "default StrokeJoin" + cap.BaseCap = LineCapSquare + cap.WidthScale = 2 + cap.StrokeJoin = LineJoinBevel + Expect.AreEqual CLng(LineCapSquare), CLng(cap.BaseCap), "BaseCap after it was set" + Expect.AreEqual 2!, cap.WidthScale, "WidthScale after it was set" + Expect.AreEqual CLng(LineJoinBevel), CLng(cap.StrokeJoin), "StrokeJoin after it was set" + Dim copy As CustomLineCap = cap.Clone() + Expect.AreEqual 2!, copy.WidthScale, "WidthScale of the clone" + Expect.Done + End Sub + + [TestCase] + Public Sub CustomLineCap_StrokeCap_SetsBothEnds() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim cap As CustomLineCap = SmallCap() + Expect.AreEqual CLng(LineCapFlat), CLng(cap.StartStrokeCap), "default StartStrokeCap" + cap.StrokeCap = LineCapRound + Dim startCap As GpLineCap, endCap As GpLineCap + Expect.AreEqual CLng(Ok), CLng(cap.GetStrokeCaps(startCap, endCap)), "status of GetStrokeCaps" + Expect.AreEqual CLng(LineCapRound), CLng(startCap), "start stroke cap after StrokeCap = LineCapRound" + Expect.AreEqual CLng(LineCapRound), CLng(endCap), "end stroke cap after StrokeCap = LineCapRound" + Expect.AreEqual CLng(LineCapRound), CLng(cap.EndStrokeCap), "EndStrokeCap after StrokeCap = LineCapRound" + Expect.Done + End Sub + + [TestCase] + Public Sub CustomLineCap_StartStrokeCap_Let_Returns() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim cap As CustomLineCap = SmallCap() + cap.StartStrokeCap = LineCapRound + Expect.AreEqual CLng(LineCapRound), CLng(cap.StartStrokeCap), "StartStrokeCap after it was set" + Expect.Done + End Sub + + [TestCase] + Public Sub CustomLineCap_EndStrokeCap_Let_Returns() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim cap As CustomLineCap = SmallCap() + cap.EndStrokeCap = LineCapRound + Expect.AreEqual CLng(LineCapRound), CLng(cap.EndStrokeCap), "EndStrokeCap after it was set" + Expect.Done + End Sub + + [TestCase] + Public Sub CustomLineCap_SetStrokeCaps_WithACap_Returns() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim cap As CustomLineCap = SmallCap() + Expect.AreEqual CLng(Ok), CLng(cap.SetStrokeCaps(LineCapRound, LineCapSquare)), "status of SetStrokeCaps(LineCapRound, LineCapSquare)" + Expect.AreEqual CLng(LineCapRound), CLng(cap.StartStrokeCap), "StartStrokeCap after SetStrokeCaps" + Expect.AreEqual CLng(LineCapSquare), CLng(cap.EndStrokeCap), "EndStrokeCap after SetStrokeCaps" + Expect.Done + End Sub + + [TestCase] + Public Sub CustomLineCap_SetStrokeCaps_WithNoCaps_ChangesNothing() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim cap As CustomLineCap = SmallCap() + cap.StrokeCap = LineCapRound + Expect.AreEqual CLng(Ok), CLng(cap.SetStrokeCaps()), "status of SetStrokeCaps with no caps" + Expect.AreEqual CLng(LineCapRound), CLng(cap.StartStrokeCap), "StartStrokeCap after SetStrokeCaps with no caps" + Expect.Done + End Sub + + [TestCase] + Public Sub CustomLineCap_EndCap_IsDrawn() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim linePen As Pen = Pen(Color(255, 255, 0, 0), 2) + linePen.CustomEndCap = SmallCap() + Dim bmp As Bitmap = Br_White(30, 10) + Dim g As Graphics = Graphics(bmp) + g.DrawLine linePen, 2, 5, 20, 5 + Expect.AreEqual "..................##..........", Br_Row(bmp, 3, 0, 29), "row 3 of a line with a small rectangle as its end cap" + Expect.AreEqual "..##################..........", Br_Row(bmp, 4, 0, 29), "row 4 of the line" + Expect.AreEqual "..................##..........", Br_Row(bmp, 6, 0, 29), "row 6 of the line" + Expect.Done + End Sub + + [TestCase] + Public Sub CustomLineCap_BaseInset_EndsTheLineEarly() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim cap As CustomLineCap = SmallCap() + cap.BaseInset = 3 + Dim linePen As Pen = Pen(Color(255, 255, 0, 0), 2) + linePen.CustomEndCap = cap + Dim bmp As Bitmap = Br_White(30, 10) + Dim g As Graphics = Graphics(bmp) + g.DrawLine linePen, 2, 5, 20, 5 + Expect.AreEqual "..############....##..........", Br_Row(bmp, 4, 0, 29), "row 4 of a line whose cap has the base inset 3" + Expect.Done + End Sub + + ' ---- ArrowCap + + [TestCase] + Public Sub ArrowCap_New_HeightComesBeforeWidth() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim cap As ArrowCap = ArrowCap(3, 4) + Expect.AreEqual 3!, cap.Height, "Height of ArrowCap(3, 4)" + Expect.AreEqual 4!, cap.Width, "Width of ArrowCap(3, 4)" + Expect.AreEqual True, cap.FillState, "FillState of ArrowCap(3, 4)" + Expect.AreEqual CLng(LineCapTriangle), CLng(cap.BaseCap), "BaseCap of a new arrow cap" + Dim sized As ArrowCap = ArrowCap(GpSizeF(9, 8), False) + Expect.AreEqual 9!, sized.Width, "Width of ArrowCap(GpSizeF(9, 8))" + Expect.AreEqual 8!, sized.Height, "Height of ArrowCap(GpSizeF(9, 8))" + Expect.AreEqual False, sized.FillState, "FillState of ArrowCap(GpSizeF(9, 8), False)" + Expect.Done + End Sub + + [TestCase] + Public Sub ArrowCap_Properties_RoundTrip() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim cap As ArrowCap = ArrowCap(3, 4) + cap.Height = 7 + cap.Width = 5 + cap.FillState = False + Expect.AreEqual 7!, cap.Height, "Height after it was set" + Expect.AreEqual 5!, cap.Width, "Width after it was set" + Expect.AreEqual False, cap.FillState, "FillState after it was set" + Dim size As GpSizeF = cap.Size + Expect.AreEqual 5!, size.Width, "Size.Width" + Expect.AreEqual 7!, size.Height, "Size.Height" + cap.Size = GpSizeF(9, 8) + Expect.AreEqual 9!, cap.Width, "Width after Size was set" + Expect.AreEqual 8!, cap.Height, "Height after Size was set" + Expect.Done + End Sub + + [TestCase] + Public Sub ArrowCap_Clone_ReturnsAUsableCopy() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim cap As ArrowCap = ArrowCap(3, 4) + Dim copy As ArrowCap = cap.Clone() + Expect.AreEqual 3!, copy.Height, "Height of the clone" + Expect.AreEqual 4!, copy.Width, "Width of the clone" + Expect.AreEqual True, copy.FillState, "FillState of the clone" + Expect.AreEqual CLng(Ok), CLng(copy.LastResult), "LastResult of the clone after reading its properties" + Expect.Done + End Sub + + [TestCase] + Public Sub ArrowCap_MiddleInset_Get_ReturnsTheSetting() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim cap As ArrowCap = ArrowCap(3, 4) + cap.MiddleInset = 1.5 + Expect.AreEqual 1.5!, cap.MiddleInset, "MiddleInset after it was set to 1.5" + Expect.Done + End Sub + + [TestCase] + Public Sub ArrowCap_StartStrokeCap_Let_Returns() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim cap As ArrowCap = ArrowCap(3, 4) + cap.StartStrokeCap = LineCapRound + Expect.AreEqual CLng(LineCapRound), CLng(cap.StartStrokeCap), "StartStrokeCap of an arrow cap after it was set" + Expect.Done + End Sub + + [TestCase] + Public Sub ArrowCap_EndStrokeCap_Let_Returns() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim cap As ArrowCap = ArrowCap(3, 4) + cap.EndStrokeCap = LineCapRound + Expect.AreEqual CLng(LineCapRound), CLng(cap.EndStrokeCap), "EndStrokeCap of an arrow cap after it was set" + Expect.Done + End Sub + + [TestCase] + Public Sub ArrowCap_SetStrokeCaps_WithACap_Returns() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim cap As ArrowCap = ArrowCap(3, 4) + Expect.AreEqual CLng(Ok), CLng(cap.SetStrokeCaps(LineCapRound, LineCapSquare)), "status of SetStrokeCaps on an arrow cap" + Expect.AreEqual CLng(LineCapRound), CLng(cap.StartStrokeCap), "StartStrokeCap of an arrow cap after SetStrokeCaps" + Expect.Done + End Sub + + [TestCase] + Public Sub ArrowCap_StrokeCap_SetsBothEnds() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim cap As ArrowCap = ArrowCap(3, 4) + cap.StrokeCap = LineCapRound + Expect.AreEqual CLng(LineCapRound), CLng(cap.StartStrokeCap), "StartStrokeCap of an arrow cap after StrokeCap = LineCapRound" + Expect.AreEqual CLng(LineCapRound), CLng(cap.EndStrokeCap), "EndStrokeCap of an arrow cap after StrokeCap = LineCapRound" + Expect.Done + End Sub + + [TestCase] + Public Sub ArrowCap_EndCap_IsDrawnAsAnArrowhead() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim linePen As Pen = Pen(Color(255, 255, 0, 0), 2) + linePen.CustomEndCap = ArrowCap(3, 3, True) + Dim bmp As Bitmap = Br_White(36, 12) + Dim g As Graphics = Graphics(bmp) + g.DrawLine linePen, 2, 6, 30, 6 + Expect.AreEqual "....##..........", Br_Row(bmp, 4, 20, 35), "row 4, columns 20 to 35, of a line with an arrowhead end cap" + Expect.AreEqual "########........", Br_Row(bmp, 5, 20, 35), "row 5" + Expect.AreEqual "##########......", Br_Row(bmp, 6, 20, 35), "row 6" + Expect.AreEqual "....####........", Br_Row(bmp, 7, 20, 35), "row 7" + Expect.Done + End Sub + +End Module diff --git a/test/package-tests/GdiPlus/Sources/Br_GradientBrushTests.twin b/test/package-tests/GdiPlus/Sources/Br_GradientBrushTests.twin new file mode 100644 index 00000000..911d767c --- /dev/null +++ b/test/package-tests/GdiPlus/Sources/Br_GradientBrushTests.twin @@ -0,0 +1,405 @@ +' LinearGradientBrush and PathGradientBrush: colours, blends, wrap mode, the path and the transformation. +[TestFixture] +Module Br_GradientBrushTests + + Private Function Linear() As LinearGradientBrush + Return New LinearGradientBrush(GpRectF(0, 0, 10, 2), Color(255, 0, 0, 0), Color(255, 255, 255, 255), LinearGradientModeHorizontal) + End Function + + ' A path gradient over a 10 by 10 square, white in the middle and black at the edge. + Private Function PathGradient() As PathGradientBrush + Dim corners() As GpPointF + Br_Corners corners, 10 + Dim brush As PathGradientBrush = New PathGradientBrush(corners) + brush.CenterColor = Color(255, 255, 255, 255) + Dim edge(1 To 1) As Color + edge(1) = Color(255, 0, 0, 0) + brush.SurroundColors = edge + Return brush + End Function + + ' ---- LinearGradientBrush + + [TestCase] + Public Sub Linear_LinearColors_HasTwoElements() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim brush As LinearGradientBrush = New LinearGradientBrush(GpPointF(0, 0), GpPointF(10, 0), Color(255, 255, 0, 0), Color(255, 0, 0, 255)) + Dim colors() As Color = brush.LinearColors + Expect.AreEqual 2, UBound(colors) - LBound(colors) + 1, "number of elements of the array LinearColors returns, for a start and an end colour" + Expect.Done + End Sub + + [TestCase] + Public Sub Linear_LinearColors_ReturnsStartThenEnd() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim brush As LinearGradientBrush = New LinearGradientBrush(GpPointF(0, 0), GpPointF(10, 0), Color(255, 255, 0, 0), Color(255, 0, 0, 255)) + Dim colors() As Color = brush.LinearColors + Expect.AreEqual "FFFF0000", Br_Hex(colors(1).argb), "colour at index 1 of LinearColors" + Expect.AreEqual "FF0000FF", Br_Hex(colors(2).argb), "colour at index 2 of LinearColors" + Expect.Done + End Sub + + [TestCase] + Public Sub Linear_LinearRGBs_HasTwoElements() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim brush As LinearGradientBrush = New LinearGradientBrush(GpPointF(0, 0), GpPointF(10, 0), Color(255, 255, 0, 0), Color(255, 0, 0, 255)) + Dim values() As Long = brush.LinearRGBs + Expect.AreEqual 2, UBound(values) - LBound(values) + 1, "number of elements of the array LinearRGBs returns, for a start and an end colour" + Expect.Done + End Sub + + [TestCase] + Public Sub Linear_LinearRGBs_AreCompleteARGBValues() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim brush As LinearGradientBrush = New LinearGradientBrush(GpPointF(0, 0), GpPointF(10, 0), Color(128, 255, 0, 0), Color(64, 0, 0, 255)) + Dim values() As Long = brush.LinearRGBs + Expect.AreEqual "80FF0000", Br_Hex(values(1)), "value at index 1 of LinearRGBs" + Expect.AreEqual "400000FF", Br_Hex(values(2)), "value at index 2 of LinearRGBs" + Expect.Done + End Sub + + [TestCase] + Public Sub Linear_SetBlend_TwoArrays_Returns() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim brush As LinearGradientBrush = Linear() + Dim factors(1 To 3) As Single, positions(1 To 3) As Single + factors(1) = 0: factors(2) = 1: factors(3) = 0 + positions(1) = 0: positions(2) = 0.5: positions(3) = 1 + Expect.AreEqual CLng(Ok), CLng(brush.SetBlend(factors, positions)), "status of SetBlend with two arrays" + Expect.AreEqual 3, brush.BlendCount, "BlendCount after SetBlend with three factors" + Expect.Done + End Sub + + [TestCase] + Public Sub Linear_SetBlend_FirstElementsAndCount_SetsTheBlend() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim brush As LinearGradientBrush = Linear() + Dim factors(1 To 3) As Single, positions(1 To 3) As Single + factors(1) = 0: factors(2) = 1: factors(3) = 0 + positions(1) = 0: positions(2) = 0.5: positions(3) = 1 + Expect.AreEqual CLng(Ok), CLng(brush.SetBlend(factors(1), positions(1), 3)), "status of SetBlend with the first elements and a count" + Expect.AreEqual 3, brush.BlendCount, "BlendCount after SetBlend with three factors" + Dim bmp As Bitmap = Bitmap(10!, 2!) + Dim g As Graphics = Graphics(bmp) + g.FillRectangle brush, 0, 0, 10, 2 + Expect.IsTrue Br_Near(0, Br_Red(bmp, 0, 0), 4), "red at x = 0 of a black-white-black blend: " & Br_Red(bmp, 0, 0) + Expect.IsTrue Br_Near(255, Br_Red(bmp, 5, 0), 30), "red at x = 5 of a black-white-black blend: " & Br_Red(bmp, 5, 0) + Dim f2() As Single, p2() As Single + Expect.AreEqual CLng(Ok), CLng(brush.GetBlend(f2, p2)), "status of GetBlend" + Expect.AreEqual 3, UBound(f2), "upper bound of the factors GetBlend returns" + Expect.AreEqual 0.5!, p2(2), "second position GetBlend returns" + Expect.Done + End Sub + + [TestCase] + Public Sub Linear_SetInterpolationColors_TwoArrays_Returns() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim brush As LinearGradientBrush = Linear() + Dim colors(1 To 3) As Color + colors(1) = Color(255, 255, 0, 0): colors(2) = Color(255, 0, 255, 0): colors(3) = Color(255, 0, 0, 255) + Dim positions(1 To 3) As Single + positions(1) = 0: positions(2) = 0.5: positions(3) = 1 + Expect.AreEqual CLng(Ok), CLng(brush.SetInterpolationColors(colors, positions)), "status of SetInterpolationColors with two arrays" + Expect.AreEqual 3, brush.InterpolationColorCount, "InterpolationColorCount after setting three colours" + Expect.Done + End Sub + + [TestCase] + Public Sub Linear_SetInterpolationColors_FirstElementsAndCount_SetsTheColours() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim brush As LinearGradientBrush = Linear() + Dim colors(1 To 3) As Color + colors(1) = Color(255, 255, 0, 0): colors(2) = Color(255, 0, 255, 0): colors(3) = Color(255, 0, 0, 255) + Dim positions(1 To 3) As Single + positions(1) = 0: positions(2) = 0.5: positions(3) = 1 + Expect.AreEqual CLng(Ok), CLng(brush.SetInterpolationColors(colors(1), positions(1), 3)), "status of SetInterpolationColors with the first elements and a count" + Expect.AreEqual 3, brush.InterpolationColorCount, "InterpolationColorCount after setting three colours" + Dim bmp As Bitmap = Bitmap(10!, 2!) + Dim g As Graphics = Graphics(bmp) + g.FillRectangle brush, 0, 0, 10, 2 + Expect.AreEqual "FFFF0000", Br_Hex(Br_Px(bmp, 0, 0)), "pixel at x = 0 of a red-green-blue blend" + Expect.AreEqual "FF00FF00", Br_Hex(Br_Px(bmp, 5, 0)), "pixel at x = 5 of a red-green-blue blend" + Dim c2() As Color, p2() As Single + Expect.AreEqual CLng(Ok), CLng(brush.GetInterpolationColors(c2, p2)), "status of GetInterpolationColors" + Expect.AreEqual "FF00FF00", Br_Hex(c2(2).argb), "second colour GetInterpolationColors returns" + Dim lc() As Color = brush.LinearColors + Expect.AreEqual "FF000000", Br_Hex(lc(1).argb), "LinearColors start colour, which preset colours do not replace" + Expect.Done + End Sub + + [TestCase] + Public Sub Linear_BlendBellShape_PeaksAtTheFocus() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim brush As LinearGradientBrush = Linear() + Expect.AreEqual CLng(Ok), CLng(brush.SetBlendBellShape(0.5)), "status of SetBlendBellShape" + Dim bmp As Bitmap = Bitmap(10!, 2!) + Dim g As Graphics = Graphics(bmp) + g.FillRectangle brush, 0, 0, 10, 2 + Expect.IsTrue Br_Red(bmp, 5, 0) > Br_Red(bmp, 1, 0) + 100, "red at the focus is well above red near the start: " & Br_Red(bmp, 5, 0) & " and " & Br_Red(bmp, 1, 0) + Expect.IsTrue Br_Red(bmp, 5, 0) > Br_Red(bmp, 9, 0) + 100, "red at the focus is well above red near the end: " & Br_Red(bmp, 5, 0) & " and " & Br_Red(bmp, 9, 0) + Expect.Done + End Sub + + [TestCase] + Public Sub Linear_WrapMode_Clamp_IsRefused() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim brush As LinearGradientBrush = Linear() + brush.WrapMode = WrapModeClamp + Expect.AreEqual CLng(InvalidParameter), CLng(brush.LastResult), "LastResult after WrapMode = WrapModeClamp" + Expect.AreEqual CLng(WrapModeTile), CLng(brush.WrapMode), "WrapMode after the refused change" + Expect.Done + End Sub + + [TestCase] + Public Sub Linear_WrapMode_TileRepeatsAndFlipMirrors() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim brush As LinearGradientBrush = New LinearGradientBrush(GpRectF(0, 0, 10, 10), Color(255, 0, 0, 0), Color(255, 255, 255, 255), LinearGradientModeHorizontal) + Dim bmp As Bitmap = Bitmap(20!, 2!) + Dim g As Graphics = Graphics(bmp) + Expect.AreEqual CLng(WrapModeTile), CLng(brush.WrapMode), "default WrapMode" + g.FillRectangle brush, 0, 0, 20, 2 + Expect.IsTrue Br_Red(bmp, 10, 0) < 60, "red at the start of the second tile with WrapModeTile: " & Br_Red(bmp, 10, 0) + brush.WrapMode = WrapModeTileFlipX + g.FillRectangle brush, 0, 0, 20, 2 + Expect.IsTrue Br_Red(bmp, 10, 0) > 200, "red at the start of the second tile with WrapModeTileFlipX: " & Br_Red(bmp, 10, 0) + Expect.Done + End Sub + + [TestCase] + Public Sub Linear_Transform_Get_ReturnsTheMatrix() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim brush As LinearGradientBrush = Linear() + brush.TranslateTransform 5, 0 + Dim m As Matrix = brush.Transform + Expect.IsFalse m Is Nothing, "Transform returns a matrix" + If Not m Is Nothing Then Expect.AreEqual 5!, m.OffsetX, "OffsetX of Transform after TranslateTransform 5, 0" + Expect.AreEqual CLng(Ok), CLng(brush.LastResult), "LastResult after reading Transform" + Expect.Done + End Sub + + [TestCase] + Public Sub Linear_GetTransform_CopiesTheMatrix() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim brush As LinearGradientBrush = Linear() + brush.TranslateTransform 5, 0 + Dim m As Matrix = New Matrix() + Expect.AreEqual CLng(Ok), CLng(brush.GetTransform(m)), "status of GetTransform" + Expect.IsTrue Br_Near(5000, CLng(m.OffsetX * 1000), 2), "OffsetX from GetTransform after TranslateTransform 5, 0: " & m.OffsetX + brush.ResetTransform + brush.GetTransform m + Expect.AreEqual 0!, m.OffsetX, "OffsetX from GetTransform after ResetTransform" + Expect.Done + End Sub + + ' ---- PathGradientBrush + + [TestCase] + Public Sub Path_New_SinglePoint_ReportsTheFailure() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim one(0 To 0) As GpPointF + one(0) = GpPointF(1, 1) + Dim brush As PathGradientBrush = New PathGradientBrush(one) + Expect.IsTrue brush.LastResult <> Ok, "LastResult of a PathGradientBrush made from a single point" + Expect.Done + End Sub + + [TestCase] + Public Sub Path_New_SinglePoint_LaterCallsFailWithInvalidParameter() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim one(0 To 0) As GpPointF + one(0) = GpPointF(1, 1) + Dim brush As PathGradientBrush = New PathGradientBrush(one) + Dim n As Long = brush.PointCount + Expect.AreEqual CLng(InvalidParameter), CLng(brush.LastResult), "LastResult after PointCount of a brush made from a single point" + Expect.Done + End Sub + + [TestCase] + Public Sub Path_SurroundColors_Get_ReturnsWhatWasSet() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim brush As PathGradientBrush = PathGradient() + Dim colors() As Color = brush.SurroundColors + Expect.AreEqual "FF000000", Br_Hex(colors(1).argb), "first colour of SurroundColors" + Expect.Done + End Sub + + [TestCase] + Public Sub Path_SurroundColors_OneColourPaintsTheWholeBoundary() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim brush As PathGradientBrush = PathGradient() + Dim bmp As Bitmap = Bitmap(10!, 10!) + Dim g As Graphics = Graphics(bmp) + g.FillRectangle brush, 0, 0, 10, 10 + Expect.AreEqual 4, brush.SurroundColorCount, "SurroundColorCount after setting one colour" + Expect.IsTrue Br_Red(bmp, 0, 5) < 20, "red on the left edge: " & Br_Red(bmp, 0, 5) + Expect.IsTrue Br_Red(bmp, 5, 5) > 235, "red in the middle: " & Br_Red(bmp, 5, 5) + Expect.Done + End Sub + + [TestCase] + Public Sub Path_SurroundColors_TooManyColours_IsRefused() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim brush As PathGradientBrush = PathGradient() + Dim colors(1 To 5) As Color + Dim i As Long + For i = 1 To 5 + colors(i) = Color(255, 255, 0, 0) + Next + brush.SurroundColors = colors + Expect.AreEqual CLng(InvalidParameter), CLng(brush.LastResult), "LastResult after setting five surround colours for four points" + Expect.Done + End Sub + + [TestCase] + Public Sub Path_GraphicsPath_Get_IsNotImplemented() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim brush As PathGradientBrush = PathGradient() + Dim p As GraphicsPath = brush.GraphicsPath + Expect.AreEqual CLng(NotImplemented), CLng(brush.LastResult), "LastResult after reading GraphicsPath" + Expect.IsTrue p Is Nothing, "GraphicsPath read returns Nothing" + Expect.Done + End Sub + + [TestCase] + Public Sub Path_GraphicsPath_Let_IsNotImplemented() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim brush As PathGradientBrush = PathGradient() + brush.GraphicsPath = Br_Square() + Expect.AreEqual CLng(NotImplemented), CLng(brush.LastResult), "LastResult after assigning GraphicsPath" + Expect.Done + End Sub + + [TestCase] + Public Sub Path_FocusScales_AndCenterPoint_RoundTrip() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim brush As PathGradientBrush = PathGradient() + Expect.AreEqual CLng(Ok), CLng(brush.SetFocusScales(0.5, 0.25)), "status of SetFocusScales" + Dim fx As Single, fy As Single + Expect.AreEqual CLng(Ok), CLng(brush.GetFocusScales(fx, fy)), "status of GetFocusScales" + Expect.AreEqual 0.5!, fx, "x focus scale" + Expect.AreEqual 0.25!, fy, "y focus scale" + brush.CenterPoint = GpPointF(2, 3) + Dim c As GpPointF = brush.CenterPoint + Expect.AreEqual 2!, c.X, "CenterPoint.X" + Expect.AreEqual 3!, c.Y, "CenterPoint.Y" + Dim r As GpRectF = brush.Rectangle + Expect.AreEqual 10!, r.Width, "width of Rectangle" + Expect.Done + End Sub + + [TestCase] + Public Sub Path_SetBlend_Returns() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim brush As PathGradientBrush = PathGradient() + Dim factors(1 To 3) As Single, positions(1 To 3) As Single + factors(1) = 0: factors(2) = 0.5: factors(3) = 1 + positions(1) = 0: positions(2) = 0.5: positions(3) = 1 + Expect.AreEqual CLng(Ok), CLng(brush.SetBlend(factors, positions)), "status of SetBlend" + Expect.AreEqual 3, brush.BlendCount, "BlendCount after SetBlend with three factors" + Expect.Done + End Sub + + [TestCase] + Public Sub Path_SetInterpolationColors_Returns() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim brush As PathGradientBrush = PathGradient() + Dim colors(1 To 3) As Color + colors(1) = Color(255, 255, 0, 0): colors(2) = Color(255, 0, 255, 0): colors(3) = Color(255, 0, 0, 255) + Dim positions(1 To 3) As Single + positions(1) = 0: positions(2) = 0.5: positions(3) = 1 + Expect.AreEqual CLng(Ok), CLng(brush.SetInterpolationColors(colors, positions)), "status of SetInterpolationColors" + Expect.AreEqual 3, brush.InterpolationColorCount, "InterpolationColorCount after setting three colours" + Expect.Done + End Sub + + [TestCase] + Public Sub Path_GetInterpolationColors_WithoutPresetColours_IsWrongState() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim brush As PathGradientBrush = PathGradient() + Dim c() As Color, p() As Single + Expect.AreEqual 0, brush.InterpolationColorCount, "InterpolationColorCount of a new brush" + Expect.AreEqual CLng(WrongState), CLng(brush.GetInterpolationColors(c, p)), "status of GetInterpolationColors without preset colours" + Expect.Done + End Sub + + [TestCase] + Public Sub Path_BlendShapes_ReturnOk() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim brush As PathGradientBrush = PathGradient() + Expect.AreEqual CLng(Ok), CLng(brush.SetBlendTriangularShape(0.5)), "status of SetBlendTriangularShape" + Expect.AreEqual CLng(Ok), CLng(brush.SetBlendBellShape(0.5)), "status of SetBlendBellShape" + Expect.Done + End Sub + + [TestCase] + Public Sub Path_WrapMode_ClampDrawsNothingOutsideAndTileRepeats() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim brush As PathGradientBrush = PathGradient() + Dim bmp As Bitmap = Bitmap(30!, 10!) + Dim g As Graphics = Graphics(bmp) + Expect.AreEqual CLng(WrapModeClamp), CLng(brush.WrapMode), "default WrapMode of the form with points" + g.Clear Color(255, 255, 0, 255) + g.FillRectangle brush, 0, 0, 30, 10 + Expect.AreEqual "FFFF00FF", Br_Hex(Br_Px(bmp, 15, 5)), "pixel outside the path with WrapModeClamp" + brush.WrapMode = WrapModeTile + g.Clear Color(255, 255, 0, 255) + g.FillRectangle brush, 0, 0, 30, 10 + Expect.IsTrue Br_Px(bmp, 15, 5) <> &HFFFF00FF, "pixel outside the path with WrapModeTile is not the cleared colour" + Expect.Done + End Sub + + [TestCase] + Public Sub Path_Transform_Get_ReturnsTheMatrix() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim brush As PathGradientBrush = PathGradient() + brush.TranslateTransform 3, 4 + Dim m As Matrix = brush.Transform + Expect.IsFalse m Is Nothing, "Transform returns a matrix" + If Not m Is Nothing Then + Expect.AreEqual 3!, m.OffsetX, "OffsetX of Transform after TranslateTransform 3, 4" + Expect.AreEqual 4!, m.OffsetY, "OffsetY of Transform after TranslateTransform 3, 4" + End If + Expect.AreEqual CLng(Ok), CLng(brush.LastResult), "LastResult after reading Transform" + Expect.Done + End Sub + + [TestCase] + Public Sub Path_FromGraphicsPath_CountsAllThePoints() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim p As GraphicsPath = New GraphicsPath() + p.AddEllipse 0, 0, 10, 10 + Dim brush As PathGradientBrush = New PathGradientBrush(p) + Expect.AreEqual CLng(Ok), CLng(brush.LastResult), "LastResult of a PathGradientBrush made from an ellipse path" + Expect.AreEqual 13, brush.PointCount, "PointCount of a brush made from an ellipse path" + Expect.Done + End Sub + +End Module diff --git a/test/package-tests/GdiPlus/Sources/Br_Helpers.twin b/test/package-tests/GdiPlus/Sources/Br_Helpers.twin new file mode 100644 index 00000000..6496833d --- /dev/null +++ b/test/package-tests/GdiPlus/Sources/Br_Helpers.twin @@ -0,0 +1,68 @@ +' Helpers of the brush, pen and path cases (Br_*). Not a fixture: no case is in it. +Module Br_Helpers + + ' A bitmap of the size, cleared to opaque white. + Public Function Br_White(ByVal Width As Long, ByVal Height As Long) As Bitmap + Dim bmp As Bitmap = Bitmap(CSng(Width), CSng(Height)) + Dim g As Graphics = Graphics(bmp) + g.Clear Color(255, 255, 255, 255) + Return bmp + End Function + + ' The colour of a pixel of the bitmap, as &HAARRGGBB. + Public Function Br_Px(ByVal Bmp As Bitmap, ByVal X As Long, ByVal Y As Long) As Long + Dim c As Color = Bmp.Pixel(X, Y) + Return c.argb + End Function + + ' The colour of a pixel, as text for a report. + Public Function Br_Hex(ByVal Argb As Long) As String + Return Right$("00000000" & Hex$(Argb), 8) + End Function + + ' The red channel of a pixel. + Public Function Br_Red(ByVal Bmp As Bitmap, ByVal X As Long, ByVal Y As Long) As Long + Dim c As Color = Bmp.Pixel(X, Y) + Return c.R + End Function + + ' A row of a bitmap as text: # for a pixel that is not opaque white, . for one that is. + Public Function Br_Row(ByVal Bmp As Bitmap, ByVal Y As Long, ByVal X0 As Long, ByVal X1 As Long) As String + Dim x As Long, s As String + For x = X0 To X1 + If Br_Px(Bmp, x, Y) = &HFFFFFFFF Then s = s & "." Else s = s & "#" + Next + Return s + End Function + + ' A column of a bitmap as text, as Br_Row. + Public Function Br_Column(ByVal Bmp As Bitmap, ByVal X As Long, ByVal Y0 As Long, ByVal Y1 As Long) As String + Dim y As Long, s As String + For y = Y0 To Y1 + If Br_Px(Bmp, X, y) = &HFFFFFFFF Then s = s & "." Else s = s & "#" + Next + Return s + End Function + + ' True when the channel values are within the tolerance of each other (antialiasing, rounding). + Public Function Br_Near(ByVal Expected As Long, ByVal Actual As Long, ByVal Tolerance As Long) As Boolean + Return Abs(Expected - Actual) <= Tolerance + End Function + + ' A path of a 10 by 10 rectangle at the origin. + Public Function Br_Square() As GraphicsPath + Dim p As GraphicsPath = New GraphicsPath() + p.AddRectangle 0, 0, 10, 10 + Return p + End Function + + ' Four corners of a square of the size, as the points of a gradient path. + Public Sub Br_Corners(ByRef Corners() As GpPointF, ByVal Size As Single) + ReDim Corners(0 To 3) + Corners(0) = GpPointF(0, 0) + Corners(1) = GpPointF(Size, 0) + Corners(2) = GpPointF(Size, Size) + Corners(3) = GpPointF(0, Size) + End Sub + +End Module diff --git a/test/package-tests/GdiPlus/Sources/Br_PathDataTests.twin b/test/package-tests/GdiPlus/Sources/Br_PathDataTests.twin new file mode 100644 index 00000000..1021fe61 --- /dev/null +++ b/test/package-tests/GdiPlus/Sources/Br_PathDataTests.twin @@ -0,0 +1,217 @@ +' PathData: a container of points and point types, and PathPointType, the type of one point. +[TestFixture] +Module Br_PathDataTests + + Private Function LineType() As PathPointType + Dim t As PathPointType + t._value = PathPointTypeLine + Return t + End Function + + ' The three points (3, 4), (5, 6), (7, 8), all of the line type. + Private Function Filled() As PathData + Dim d As PathData = PathData(3) + d.Point(1) = GpPointF(3, 4) + d.Point(2) = GpPointF(5, 6) + d.Point(3) = GpPointF(7, 8) + d.Type(1) = LineType() + d.Type(2) = LineType() + d.Type(3) = LineType() + Return d + End Function + + ' ---- creating and sizing + + [TestCase] + Public Sub New_Count_AndAllocate() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim a As PathData = PathData(3) + Dim b As PathData = New PathData + Expect.AreEqual 3, a.Count, "Count of PathData(3)" + Expect.AreEqual 0, b.Count, "Count of New PathData" + Expect.AreEqual CLng(Ok), CLng(b.Allocate(5)), "status of Allocate(5)" + Expect.AreEqual 5, b.Count, "Count after Allocate(5)" + Expect.AreEqual 0, CLng(b.Allocate(0)), "status of Allocate(0)" + Expect.AreEqual 0, b.Count, "Count after Allocate(0)" + Expect.Done + End Sub + + [TestCase] + Public Sub New_TypedPoints_CopiesThem() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim tp(1 To 3) As TypedPoint + tp(1).pos = GpPointF(0, 0) + tp(2).pos = GpPointF(10, 0) + tp(3).pos = GpPointF(10, 10) + tp(2).type._value = PathPointTypeLine + tp(3).type._value = PathPointTypeLine + Dim d As PathData = PathData(tp) + Dim pts() As GpPointF = d.Points + Expect.AreEqual 3, d.Count, "Count of a PathData made from three typed points" + Expect.AreEqual 10!, pts(3).X, "X of the third point" + Expect.AreEqual 10!, pts(3).Y, "Y of the third point" + Expect.Done + End Sub + + ' ---- reading through the views + + [TestCase] + Public Sub Points_AndTypes_AreViewsFromIndexOne() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim d As PathData = Filled() + Dim pts() As GpPointF = d.Points + Dim ty() As PathPointType = d.Types + Expect.AreEqual 1, LBound(pts), "lower bound of Points" + Expect.AreEqual 3, UBound(pts), "upper bound of Points" + Expect.AreEqual 5!, pts(2).X, "X of the second point" + Expect.AreEqual 6!, pts(2).Y, "Y of the second point" + Expect.AreEqual 1, CLng(ty(2)._value), "type byte of the second point" + Expect.Done + End Sub + + [TestCase] + Public Sub PointsPtr_AndTypesPtr_AreZeroWithoutRoom() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim noRoom As PathData = New PathData + Expect.IsTrue noRoom.PointsPtr = 0, "PointsPtr of a PathData with no room" + Expect.IsTrue noRoom.TypesPtr = 0, "TypesPtr of a PathData with no room" + Dim d As PathData = Filled() + Expect.IsTrue d.PointsPtr <> 0, "PointsPtr of a PathData with room" + Expect.IsTrue d.TypesPtr <> 0, "TypesPtr of a PathData with room" + Expect.Done + End Sub + + ' ---- the 32-bit access violation + + [TestCase] + Public Sub Point_Get_ReturnsThePoint() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim d As PathData = Filled() + Dim p As GpPointF = d.Point(2) + Expect.AreEqual 5!, p.X, "X of Point(2)" + Expect.AreEqual 6!, p.Y, "Y of Point(2)" + Expect.Done + End Sub + + [TestCase] + Public Sub TypedPoint_Get_ReturnsThePointAndItsType() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim d As PathData = Filled() + Dim tp As TypedPoint = d.TypedPoint(2) + Expect.AreEqual 5!, tp.pos.X, "X of TypedPoint(2)" + Expect.AreEqual 1, CLng(tp.type._value), "type byte of TypedPoint(2)" + Expect.Done + End Sub + + [TestCase] + Public Sub TypedPoints_Get_ReturnsACopy() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim d As PathData = Filled() + Dim all() As TypedPoint = d.TypedPoints + Expect.AreEqual 3, UBound(all), "upper bound of TypedPoints" + Expect.AreEqual 7!, all(3).pos.X, "X of the third typed point" + Expect.Done + End Sub + + ' ---- indexes outside 1 to Count + + [TestCase] + Public Sub Type_Get_OutOfRange_ReturnsZero() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim d As PathData = Filled() + Dim t As PathPointType = d.Type(0) + Expect.AreEqual 0, CLng(t._value), "type byte of Type(0)" + t = d.Type(4) + Expect.AreEqual 0, CLng(t._value), "type byte of Type(4) of three points" + Expect.Done + End Sub + + [TestCase] + Public Sub Point_Let_OutOfRange_LeavesTheMemoryBesideTheArrayAlone() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim d As PathData = Filled() + Dim before As Long, after As Long + GetMem4 d.PointsPtr + 24, before + d.Point(4) = GpPointF(1234.5, 6789.25) + GetMem4 d.PointsPtr + 24, after + Expect.IsTrue before = after, "the four bytes after the three points of a PathData are as they were after Point(4) was set" + Expect.Done + End Sub + + [TestCase] + Public Sub Type_Let_OutOfRange_LeavesTheMemoryBesideTheArrayAlone() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim d As PathData = PathData(8) + Dim before As Byte, after As Byte + GetMem1 d.TypesPtr + 8, before + Dim t As PathPointType + t._value = 7 + d.Type(9) = t + GetMem1 d.TypesPtr + 8, after + Expect.IsTrue before = after, "the byte after the eight types of a PathData is as it was after Type(9) was set" + Expect.Done + End Sub + + [TestCase] + Public Sub TypedPoint_Let_OutOfRange_LeavesTheMemoryBesideTheArraysAlone() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim d As PathData = PathData(8) + Dim beforePoints As Long, afterPoints As Long + Dim beforeTypes As Byte, afterTypes As Byte + GetMem4 d.PointsPtr + 64, beforePoints + GetMem1 d.TypesPtr + 8, beforeTypes + Dim tp As TypedPoint + tp.pos = GpPointF(1234.5, 6789.25) + tp.type._value = 7 + d.TypedPoint(9) = tp + GetMem4 d.PointsPtr + 64, afterPoints + GetMem1 d.TypesPtr + 8, afterTypes + Expect.IsTrue beforePoints = afterPoints, "the four bytes after the eight points of a PathData are as they were after TypedPoint(9) was set" + Expect.IsTrue beforeTypes = afterTypes, "the byte after the eight types of a PathData is as it was after TypedPoint(9) was set" + Expect.Done + End Sub + + ' ---- PathPointType + + [TestCase] + Public Sub PathPointType_Properties_ReadTheByte() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim t As PathPointType + t._value = PathPointTypeBezier Or PathPointTypeCloseSubpath + Expect.AreEqual CLng(PathPointTypeBezier), CLng(t.PointType), "PointType of a closing Bezier point" + Expect.AreEqual True, t.CloseSubpath, "CloseSubpath of a closing Bezier point" + Expect.AreEqual False, t.PathMarker, "PathMarker of a closing Bezier point" + t._value = PathPointTypeLine Or PathPointTypePathMarker + Expect.AreEqual CLng(PathPointTypeLine), CLng(t.PointType), "PointType of a marker line point" + Expect.AreEqual True, t.PathMarker, "PathMarker of a marker line point" + Expect.AreEqual False, t.CloseSubpath, "CloseSubpath of a marker line point" + Expect.Done + End Sub + + [TestCase] + Public Sub PathPointType_ReadThroughAVariable_OfAnArrayElement() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim p As GraphicsPath = New GraphicsPath() + p.AddRectangle 0, 0, 5, 5 + Dim types() As PathPointType + p.GetTypes types + Dim t As PathPointType = types(4) + Expect.AreEqual CLng(PathPointTypeLine), CLng(t.PointType), "PointType of the fourth point of a rectangle" + Expect.AreEqual True, t.CloseSubpath, "CloseSubpath of the fourth point of a rectangle" + Expect.Done + End Sub + +End Module diff --git a/test/package-tests/GdiPlus/Sources/Br_PathIteratorTests.twin b/test/package-tests/GdiPlus/Sources/Br_PathIteratorTests.twin new file mode 100644 index 00000000..b4088d29 --- /dev/null +++ b/test/package-tests/GdiPlus/Sources/Br_PathIteratorTests.twin @@ -0,0 +1,281 @@ +' PathIterator: the figures, the marker sections, the runs of one point type, and copying points out. +[TestFixture] +Module Br_PathIteratorTests + + ' A rectangle, a marker, a line, then an ellipse in a figure of its own: 19 points, three figures. + Private Function ThreeFigures() As GraphicsPath + Dim p As GraphicsPath = New GraphicsPath() + p.AddRectangle 0, 0, 10, 10 + p.SetMarker + p.AddLine 20, 20, 30, 30 + p.StartFigure + p.AddEllipse 40, 40, 10, 10 + Return p + End Function + + ' ---- the properties + + [TestCase] + Public Sub Count_SubpathCount_AndHasCurve() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim it As PathIterator = PathIterator(ThreeFigures()) + Expect.AreEqual 19, it.Count, "Count" + Expect.AreEqual 3, it.SubpathCount, "SubpathCount" + Expect.AreEqual True, it.HasCurve, "HasCurve" + Dim noFigures As PathIterator = PathIterator(New GraphicsPath()) + Expect.AreEqual 0, noFigures.SubpathCount, "SubpathCount of an empty path" + Expect.Done + End Sub + + ' ---- figures + + [TestCase] + Public Sub NextSubpath_UDT_ListsTheFigures() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim it As PathIterator = PathIterator(ThreeFigures()) + Dim sp As SubPath + Dim s As String + Do + sp = it.NextSubpath() + If sp.resultCount = 0 Then Exit Do + s = s & sp.startIndex & ".." & sp.endIndex & " " & sp.isClosed & "; " + Loop + Expect.AreEqual "0..3 True; 4..5 False; 6..18 True; ", s, "figures listed by NextSubpath" + Expect.Done + End Sub + + [TestCase] + Public Sub NextSubpath_Direct_ListsTheFigures() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim p As GraphicsPath = New GraphicsPath() + p.AddRectangle 0, 0, 10, 10 + p.AddLine 20, 20, 30, 30 + Dim it As PathIterator = PathIterator(p) + Dim first As Long, last As Long, closed As Boolean, n As Long + n = it.NextSubpath(first, last, closed) + Expect.AreEqual "4 0 3 True", n & " " & first & " " & last & " " & closed, "first figure, as count, first index, last index and closed" + n = it.NextSubpath(first, last, closed) + Expect.AreEqual "2 4 5 False", n & " " & first & " " & last & " " & closed, "second figure" + n = it.NextSubpath(first, last, closed) + Expect.AreEqual 0, n, "count at the end of the path" + Expect.Done + End Sub + + [TestCase] + Public Sub Rewind_StartsAgain() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim it As PathIterator = PathIterator(ThreeFigures()) + Dim sp As SubPath = it.NextSubpath() + sp = it.NextSubpath() + Expect.AreEqual CLng(Ok), CLng(it.Rewind()), "status of Rewind" + sp = it.NextSubpath() + Expect.AreEqual 0, sp.startIndex, "start index of the first figure after Rewind" + Expect.Done + End Sub + + [TestCase] + Public Sub NextSubpathPath_UDT_ReturnsTheFigure() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim it As PathIterator = PathIterator(ThreeFigures()) + Dim sp As SubPathPath = it.NextSubpathPath() + Expect.IsFalse sp.path Is Nothing, "path of the first figure" + If Not sp.path Is Nothing Then Expect.AreEqual 4, sp.path.PointCount, "PointCount of the first figure, a rectangle" + Expect.AreEqual True, sp.isClosed, "isClosed of the first figure" + Expect.Done + End Sub + + [TestCase] + Public Sub NextSubpathPath_Direct_ReturnsTheFigure() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim it As PathIterator = PathIterator(ThreeFigures()) + Dim figure As GraphicsPath + Dim closed As Boolean + Dim n As Long = it.NextSubpathPath(figure, closed) + Expect.AreEqual 4, n, "count of the first figure" + Expect.IsFalse figure Is Nothing, "path of the first figure" + If Not figure Is Nothing Then Expect.AreEqual 4, figure.PointCount, "PointCount of the first figure, a rectangle" + Expect.Done + End Sub + + ' ---- sections between markers + + [TestCase] + Public Sub NextMarker_UDT_ListsTheSections() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim it As PathIterator = PathIterator(ThreeFigures()) + Dim mk As Marker + Dim s As String + Do + mk = it.NextMarker() + If mk.resultCount = 0 Then Exit Do + s = s & mk.startIndex & ".." & mk.endIndex & "; " + Loop + Expect.AreEqual "0..3; 4..18; ", s, "sections listed by NextMarker" + Expect.Done + End Sub + + [TestCase] + Public Sub NextMarker_Direct_ListsTheSections() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim it As PathIterator = PathIterator(ThreeFigures()) + Dim first As Long, last As Long + Expect.AreEqual 4, it.NextMarker(first, last), "count of the first section" + Expect.AreEqual "0 3", first & " " & last, "indexes of the first section" + Expect.AreEqual 15, it.NextMarker(first, last), "count of the second section" + Expect.AreEqual "4 18", first & " " & last, "indexes of the second section" + Expect.Done + End Sub + + [TestCase] + Public Sub NextMarkerPath_UDT_ReturnsTheSection() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim it As PathIterator = PathIterator(ThreeFigures()) + Dim mp As MarkerPath = it.NextMarkerPath() + Expect.IsFalse mp.path Is Nothing, "path of the first section" + If Not mp.path Is Nothing Then Expect.AreEqual 4, mp.path.PointCount, "PointCount of the first section" + Expect.Done + End Sub + + [TestCase] + Public Sub NextMarkerPath_Direct_ReturnsTheCount() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim it As PathIterator = PathIterator(ThreeFigures()) + Dim section As GraphicsPath + Expect.AreEqual 4, it.NextMarkerPath(section), "count of the first section" + Expect.Done + End Sub + + [TestCase] + Public Sub NextMarkerPath_Direct_ReturnsAUsablePath() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim it As PathIterator = PathIterator(ThreeFigures()) + Dim section As GraphicsPath + Dim n As Long = it.NextMarkerPath(section) + Expect.IsFalse section Is Nothing, "path of the first section" + If Not section Is Nothing Then Expect.AreEqual 4, section.PointCount, "PointCount of the first section" + Expect.Done + End Sub + + ' ---- runs of one type + + [TestCase] + Public Sub NextPathType_UDT_ListsTheRunsOfAFigure() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim p As GraphicsPath = New GraphicsPath() + p.AddLine 0, 0, 10, 0 + p.AddBezier 10, 0, 12, 5, 14, 5, 16, 0 + Dim it As PathIterator = PathIterator(p) + Dim sp As SubPath = it.NextSubpath() + Dim run As PathType + Dim s As String + Do + run = it.NextPathType() + If run.resultCount = 0 Then Exit Do + s = s & CLng(run.type.PointType) & ": " & run.startIndex & ".." & run.endIndex & "; " + Loop + Expect.AreEqual "1: 0..1; 3: 1..4; ", s, "runs of a line followed by a Bezier curve" + Expect.Done + End Sub + + [TestCase] + Public Sub NextPathType_BeforeNextSubpath_ReturnsZero() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim it As PathIterator = PathIterator(ThreeFigures()) + Dim run As PathType = it.NextPathType() + Expect.AreEqual 0, run.resultCount, "resultCount of NextPathType before NextSubpath" + Expect.Done + End Sub + + ' ---- copying points + + [TestCase] + Public Sub Enumerate_UDT_CopiesTheWholePathOrAPart() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim p As GraphicsPath = New GraphicsPath() + Dim pts(1 To 3) As GpPointF + pts(1) = GpPointF(0, 0): pts(2) = GpPointF(10, 0): pts(3) = GpPointF(10, 10) + p.AddLines pts + Dim it As PathIterator = PathIterator(p) + Dim all As TypedPoints = it.Enumerate() + Expect.AreEqual "3 3", all.resultCount & " " & UBound(all.points), "count and upper bound of Enumerate()" + Dim part As TypedPoints = it.Enumerate(1, 2) + Expect.AreEqual 2, part.resultCount, "count of Enumerate(1, 2)" + Expect.AreEqual 10!, part.points(1).pos.X, "X of the first point of Enumerate(1, 2)" + Expect.Done + End Sub + + [TestCase] + Public Sub Enumerate_Direct_SizesTheArraysFromOne() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim p As GraphicsPath = New GraphicsPath() + Dim pts(1 To 3) As GpPointF + pts(1) = GpPointF(0, 0): pts(2) = GpPointF(10, 0): pts(3) = GpPointF(10, 10) + p.AddLines pts + Dim it As PathIterator = PathIterator(p) + Dim outPts() As GpPointF + Dim outTypes() As PathPointType + Expect.AreEqual 3, it.Enumerate(outPts, outTypes), "count Enumerate returns" + Expect.AreEqual 1, LBound(outPts), "lower bound of the points" + Expect.AreEqual 3, UBound(outTypes), "upper bound of the types" + Expect.AreEqual 10!, outPts(3).Y, "Y of the third point" + Expect.Done + End Sub + + [TestCase] + Public Sub Enumerate_OnlyOneIndex_IsRefused() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim it As PathIterator = PathIterator(ThreeFigures()) + Dim part As TypedPoints = it.Enumerate(1) + Expect.AreEqual CLng(InvalidParameter), CLng(it.LastResult), "LastResult after Enumerate with a start index only" + Expect.Done + End Sub + + [TestCase] + Public Sub Enumerate_EmptyPath_RaisesUnspecifiedError() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim it As PathIterator = PathIterator(New GraphicsPath()) + Dim errUDT As Long, errDirect As Long + Dim all As TypedPoints + On Error Resume Next + all = it.Enumerate() + errUDT = Err.Number + Err.Clear + Dim outPts() As GpPointF + Dim outTypes() As PathPointType + Dim n As Long = it.Enumerate(outPts, outTypes) + errDirect = Err.Number + Err.Clear + On Error GoTo 0 + Expect.AreEqual -2147467259, errUDT, "error raised by the UDT form of Enumerate on an empty path" + Expect.AreEqual -2147467259, errDirect, "error raised by the direct form of Enumerate on an empty path" + Expect.Done + End Sub + + [TestCase] + Public Sub CopyData_DoesWhatEnumerateDoes() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim it As PathIterator = PathIterator(ThreeFigures()) + Dim part As TypedPoints = it.CopyData(0, 3) + Expect.AreEqual 4, part.resultCount, "count of CopyData(0, 3)" + Expect.Done + End Sub + +End Module diff --git a/test/package-tests/GdiPlus/Sources/Br_PathTests.twin b/test/package-tests/GdiPlus/Sources/Br_PathTests.twin new file mode 100644 index 00000000..35bbbbbc --- /dev/null +++ b/test/package-tests/GdiPlus/Sources/Br_PathTests.twin @@ -0,0 +1,385 @@ +' GraphicsPath: construction from point data, the figures, reading a path back, hit testing and the operations on a path. +[TestFixture] +Module Br_PathTests + + ' The error number of a run-time error, 0 for none. + Private Function CaughtError(ByVal Path As GraphicsPath, ByVal What As String) As Long + Dim pts() As GpPointF + Dim ty() As PathPointType + On Error Resume Next + If What = "points" Then Path.GetPoints pts Else Path.GetTypes ty + CaughtError = Err.Number + Err.Clear + End Function + + ' ---- creating a path from point data + + [TestCase] + Public Sub New_PathData_CreatesThePath() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim d As PathData = PathData(3) + d.Point(1) = GpPointF(0, 0) + d.Point(2) = GpPointF(10, 0) + d.Point(3) = GpPointF(10, 10) + Dim t As PathPointType + t._value = PathPointTypeStart + d.Type(1) = t + t._value = PathPointTypeLine + d.Type(2) = t + d.Type(3) = t + Dim p As GraphicsPath = GraphicsPath(d) + Expect.AreEqual 3, p.PointCount, "PointCount of a path made from a PathData of three points" + Expect.Done + End Sub + + [TestCase] + Public Sub New_TypedPoints_CreatesThePath() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim tp(1 To 3) As TypedPoint + tp(1).pos = GpPointF(0, 0) + tp(2).pos = GpPointF(10, 0) + tp(3).pos = GpPointF(10, 10) + tp(1).type._value = PathPointTypeStart + tp(2).type._value = PathPointTypeLine + tp(3).type._value = PathPointTypeLine + Dim p As GraphicsPath = GraphicsPath(tp) + Expect.AreEqual 3, p.PointCount, "PointCount of a path made from three typed points" + Expect.Done + End Sub + + [TestCase] + Public Sub New_PointsAndTypes_CreatesThePath() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim pts(1 To 3) As GpPointF + pts(1) = GpPointF(0, 0): pts(2) = GpPointF(10, 0): pts(3) = GpPointF(10, 10) + Dim ty(1 To 3) As PathPointType + ty(1)._value = PathPointTypeStart + ty(2)._value = PathPointTypeLine + ty(3)._value = PathPointTypeLine + Dim p As GraphicsPath = GraphicsPath(pts, ty) + Expect.AreEqual 3, p.PointCount, "PointCount of a path made from three GpPointF and their types" + Expect.Done + End Sub + + [TestCase] + Public Sub New_GpPointsAndTypes_CreatesThePath() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim pts(1 To 3) As GpPoint + pts(1) = GpPoint(0, 0): pts(2) = GpPoint(10, 0): pts(3) = GpPoint(10, 10) + Dim ty(1 To 3) As PathPointType + ty(1)._value = PathPointTypeStart + ty(2)._value = PathPointTypeLine + ty(3)._value = PathPointTypeLine + Dim p As GraphicsPath = GraphicsPath(pts, ty) + Expect.AreEqual CLng(Ok), CLng(p.LastResult), "LastResult of a path made from three GpPoint and their types" + Expect.AreEqual 3, p.PointCount, "PointCount of a path made from three GpPoint and their types" + Expect.Done + End Sub + + [TestCase] + Public Sub PathData_Get_ReturnsThePoints() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim p As GraphicsPath = Br_Square() + Dim d As PathData = p.PathData + Expect.IsFalse d Is Nothing, "PathData returns an object for a path of four points" + If Not d Is Nothing Then Expect.AreEqual 4, d.Count, "Count of the PathData of a rectangle" + Expect.AreEqual CLng(Ok), CLng(p.LastResult), "LastResult after reading PathData" + Expect.Done + End Sub + + [TestCase] + Public Sub GetPoints_EmptyPath_RaisesUnspecifiedError() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim p As GraphicsPath = New GraphicsPath() + Expect.AreEqual -2147467259, CaughtError(p, "points"), "error raised by GetPoints on an empty path" + Expect.Done + End Sub + + [TestCase] + Public Sub GetTypes_EmptyPath_RaisesUnspecifiedError() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim p As GraphicsPath = New GraphicsPath() + Expect.AreEqual -2147467259, CaughtError(p, "types"), "error raised by GetTypes on an empty path" + Expect.Done + End Sub + + ' ---- figures, markers and reading a path back + + [TestCase] + Public Sub Types_OfTwoFiguresWithAClosedOneAndAMarker() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim p As GraphicsPath = New GraphicsPath() + p.AddLine 0, 0, 5, 5 + p.StartFigure + p.AddLine 10, 10, 15, 15 + p.AddLine 15, 15, 20, 10 + p.CloseFigure + p.SetMarker + Dim ty() As PathPointType + p.GetTypes ty + Dim t As PathPointType + Dim i As Long, s As String + For i = 1 To UBound(ty) + t = ty(i) + s = s & CLng(t.PointType) & IIf(t.CloseSubpath, "c", "") & IIf(t.PathMarker, "m", "") & " " + Next + Expect.AreEqual "0 1 0 1 1cm ", s, "types of the points of a path of two figures, the second closed and the last point a marker" + Expect.Done + End Sub + + [TestCase] + Public Sub Shapes_PointCounts() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim p As GraphicsPath = New GraphicsPath() + p.AddRectangle 0, 0, 10, 10 + Expect.AreEqual 4, p.PointCount, "PointCount after AddRectangle" + p.AddEllipse 20, 0, 10, 10 + Expect.AreEqual 17, p.PointCount, "PointCount after AddEllipse (13 points)" + Dim tri(1 To 3) As GpPointF + tri(1) = GpPointF(40, 0): tri(2) = GpPointF(50, 0): tri(3) = GpPointF(45, 8) + p.AddPolygon tri + Expect.AreEqual 20, p.PointCount, "PointCount after AddPolygon of three points" + Expect.Done + End Sub + + [TestCase] + Public Sub Lines_AddLines_AndBeziers_PointCounts() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim p As GraphicsPath = New GraphicsPath() + p.AddLine 0, 0, 10, 10 + Expect.AreEqual 2, p.PointCount, "PointCount after AddLine" + Dim pts(1 To 3) As GpPointF + pts(1) = GpPointF(20, 0): pts(2) = GpPointF(30, 10): pts(3) = GpPointF(20, 20) + p.AddLines pts + Expect.AreEqual 5, p.PointCount, "PointCount after AddLines of three points to a figure" + Dim b As GraphicsPath = New GraphicsPath() + b.AddBezier 0, 0, 5, 10, 15, 10, 20, 0 + Expect.AreEqual 4, b.PointCount, "PointCount after AddBezier" + Expect.Done + End Sub + + [TestCase] + Public Sub AddBeziers_NeedsThreeNPlusOnePoints() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim pts(1 To 7) As GpPointF + pts(1) = GpPointF(0, 0): pts(2) = GpPointF(5, 10): pts(3) = GpPointF(15, 10): pts(4) = GpPointF(20, 0) + pts(5) = GpPointF(25, -10): pts(6) = GpPointF(35, -10): pts(7) = GpPointF(40, 0) + Dim q As GraphicsPath = New GraphicsPath() + Expect.AreEqual CLng(Ok), CLng(q.AddBeziers(pts)), "status of AddBeziers with seven points" + Expect.AreEqual 7, q.PointCount, "PointCount after AddBeziers with seven points" + Dim r(1 To 3) As GpPointF + Expect.AreEqual CLng(InvalidParameter), CLng(q.AddBeziers(r)), "status of AddBeziers with three points" + Expect.Done + End Sub + + [TestCase] + Public Sub Curves_PointCounts() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim pts(1 To 3) As GpPointF + pts(1) = GpPointF(0, 0): pts(2) = GpPointF(5, 10): pts(3) = GpPointF(10, 0) + Dim p As GraphicsPath = New GraphicsPath() + p.AddCurve pts + Expect.AreEqual 7, p.PointCount, "PointCount after AddCurve of three points" + Dim c As GraphicsPath = New GraphicsPath() + c.AddClosedCurve pts + Expect.AreEqual 10, c.PointCount, "PointCount after AddClosedCurve of three points" + Expect.Done + End Sub + + [TestCase] + Public Sub Clone_IsIndependent() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim p As GraphicsPath = New GraphicsPath() + p.AddRectangle 1, 2, 10, 20 + Dim c As GraphicsPath = p.Clone + p.AddRectangle 0, 0, 5, 5 + Expect.AreEqual 8, p.PointCount, "PointCount of the original after a rectangle was added" + Expect.AreEqual 4, c.PointCount, "PointCount of the clone" + Expect.Done + End Sub + + [TestCase] + Public Sub FillMode_WindingFillsTheInnerRectangleAlternateDoesNot() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim p As GraphicsPath = New GraphicsPath() + p.AddRectangle 0, 0, 10, 10 + p.AddRectangle 2, 2, 6, 6 + Expect.AreEqual False, p.IsVisible(5, 5), "IsVisible of the inner rectangle with FillModeAlternate" + p.FillMode = FillModeWinding + Expect.AreEqual True, p.IsVisible(5, 5), "IsVisible of the inner rectangle with FillModeWinding" + p.Reset + Expect.AreEqual CLng(FillModeAlternate), CLng(p.FillMode), "FillMode after Reset" + Expect.Done + End Sub + + [TestCase] + Public Sub LastPoint_IsTheEndOfTheLastFigure() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim p As GraphicsPath = New GraphicsPath() + p.AddLine 0, 0, 10, 20 + Dim pt As GpPointF = p.LastPoint + Expect.AreEqual 10!, pt.X, "LastPoint.X" + Expect.AreEqual 20!, pt.Y, "LastPoint.Y" + Expect.Done + End Sub + + [TestCase] + Public Sub GetPoints_ListsThePointsFromIndexOne() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim p As GraphicsPath = New GraphicsPath() + p.AddRectangle 1, 2, 10, 20 + Dim pts() As GpPointF + p.GetPoints pts + Expect.AreEqual 1, LBound(pts), "lower bound of GetPoints" + Expect.AreEqual 4, UBound(pts), "upper bound of GetPoints" + Expect.AreEqual 11!, pts(2).X, "X of the second point" + Expect.AreEqual 22!, pts(3).Y, "Y of the third point" + Expect.Done + End Sub + + ' ---- changing a path + + [TestCase] + Public Sub Transform_MultipliesEveryPoint() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim p As GraphicsPath = New GraphicsPath() + p.AddLine 1, 1, 2, 2 + Dim m As Matrix = Matrix(2, 0, 0, 3, 10, 20) + p.Transform m + Dim pt As GpPointF = p.LastPoint + Expect.AreEqual 14!, pt.X, "LastPoint.X after Transform" + Expect.AreEqual 26!, pt.Y, "LastPoint.Y after Transform" + Expect.Done + End Sub + + [TestCase] + Public Sub Flatten_ReplacesTheCurvesWithLines() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim p As GraphicsPath = New GraphicsPath() + p.AddEllipse 0, 0, 100, 100 + Dim it As PathIterator = PathIterator(p) + Expect.IsTrue it.HasCurve, "HasCurve of an ellipse" + p.Flatten + Expect.IsTrue p.PointCount > 13, "PointCount of a flattened ellipse: " & p.PointCount + Dim flat As PathIterator = PathIterator(p) + Expect.IsFalse flat.HasCurve, "HasCurve of a flattened ellipse" + Expect.Done + End Sub + + [TestCase] + Public Sub Widen_ReplacesTheLineWithTheOutlineOfItsStroke() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim p As GraphicsPath = New GraphicsPath() + p.AddLine 0, 0, 10, 0 + p.Widen Pen(Color(255, 255, 0, 0), 2) + Expect.AreEqual 4, p.PointCount, "PointCount of a widened line" + Expect.AreEqual CLng(FillModeWinding), CLng(p.FillMode), "FillMode of a widened line" + Dim b As GpRectF + p.GetWorldBounds b, Nothing, Nothing + Expect.AreEqual -1!, b.Y, "Y of the bounds of a widened line" + Expect.AreEqual 2!, b.Height, "Height of the bounds of a widened line" + Expect.Done + End Sub + + [TestCase] + Public Sub Outline_KeepsTheOuterBoundaryOfOverlappingFigures() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim p As GraphicsPath = New GraphicsPath() + p.AddRectangle 0, 0, 10, 10 + p.AddRectangle 5, 5, 10, 10 + p.FillMode = FillModeWinding + Dim before As PathIterator = PathIterator(p) + Expect.AreEqual 2, before.SubpathCount, "figures before Outline" + p.Outline + Dim after As PathIterator = PathIterator(p) + Expect.AreEqual 1, after.SubpathCount, "figures after Outline" + Expect.Done + End Sub + + [TestCase] + Public Sub Warp_MapsTheSourceRectangleToTheQuadrilateral() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim p As GraphicsPath = New GraphicsPath() + p.AddRectangle 0, 0, 10, 10 + Dim dst(1 To 4) As GpPointF + dst(1) = GpPointF(0, 0): dst(2) = GpPointF(20, 0) + dst(3) = GpPointF(0, 10): dst(4) = GpPointF(20, 10) + Expect.AreEqual CLng(Ok), CLng(p.Warp(dst, GpRectF(0, 0, 10, 10))), "status of Warp" + Dim b As GpRectF + p.GetWorldBounds b, Nothing, Nothing + Expect.AreEqual 20!, CSng(Round(b.Width, 2)), "width of the bounds after Warp" + Expect.AreEqual 10!, CSng(Round(b.Height, 2)), "height of the bounds after Warp" + Expect.Done + End Sub + + ' ---- bounds and hit testing + + [TestCase] + Public Sub GetWorldBounds_OfARectangle() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim p As GraphicsPath = New GraphicsPath() + p.AddRectangle 1, 2, 10, 20 + Dim b As GpRectF + p.GetWorldBounds b, Nothing, Nothing + Expect.AreEqual 1!, b.X, "X of the bounds" + Expect.AreEqual 2!, b.Y, "Y of the bounds" + Expect.AreEqual 10!, b.Width, "Width of the bounds" + Expect.AreEqual 20!, b.Height, "Height of the bounds" + Expect.Done + End Sub + + [TestCase] + Public Sub IsVisible_AndIsOutlineVisible() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim p As GraphicsPath = Br_Square() + Dim pn As Pen = Pen(Color(255, 255, 0, 0), 2) + Expect.AreEqual True, p.IsVisible(5, 5), "IsVisible inside the rectangle" + Expect.AreEqual False, p.IsVisible(15, 15), "IsVisible outside the rectangle" + Expect.AreEqual True, p.IsOutlineVisible(0, 5, pn), "IsOutlineVisible on the edge" + Expect.AreEqual False, p.IsOutlineVisible(5, 5, pn), "IsOutlineVisible inside" + Expect.Done + End Sub + + [TestCase] + Public Sub FillPath_FillsTheShape() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = Br_White(40, 40) + Dim g As Graphics = Graphics(bmp) + Dim p As GraphicsPath = New GraphicsPath() + p.AddArc 4, 6, 16, 16, 180, 180 + p.AddArc 20, 6, 16, 16, 180, 180 + Dim tip(1 To 3) As GpPointF + tip(1) = GpPointF(36, 14): tip(2) = GpPointF(20, 36): tip(3) = GpPointF(4, 14) + p.AddLines tip + p.CloseFigure + g.FillPath SolidBrush(Color(255, 255, 0, 0)), p + Expect.AreEqual "FFFF0000", Br_Hex(Br_Px(bmp, 20, 20)), "pixel inside the heart" + Expect.AreEqual "FFFFFFFF", Br_Hex(Br_Px(bmp, 2, 2)), "pixel outside the heart" + Expect.Done + End Sub + +End Module diff --git a/test/package-tests/GdiPlus/Sources/Br_PenTests.twin b/test/package-tests/GdiPlus/Sources/Br_PenTests.twin new file mode 100644 index 00000000..6d3d8d12 --- /dev/null +++ b/test/package-tests/GdiPlus/Sources/Br_PenTests.twin @@ -0,0 +1,359 @@ +' Pen: width and unit, the brush it paints with, caps (including custom and arrow caps), dashes, joins and the transformation. +[TestFixture] +Module Br_PenTests + + Private Function ArrowPen() As Pen + Dim linePen As Pen = Pen(Color(255, 255, 0, 0), 2) + linePen.CustomStartCap = ArrowCap(3, 4) + Return linePen + End Function + + ' ---- width and unit + + [TestCase] + Public Sub New_WorldWidth_ScalesWithTheTransformation() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = Br_White(10, 10) + Dim g As Graphics = Graphics(bmp) + g.ScaleTransform 3, 3 + g.DrawLine Pen(Color(255, 255, 0, 0), 1), 0, 1.5, 3, 1.5 + Expect.AreEqual "...###....", Br_Column(bmp, 2, 0, 9), "rows painted by a world pen of width 1 under ScaleTransform 3, 3" + Expect.Done + End Sub + + [TestCase] + Public Sub New_PixelWidth_IgnoresTheTransformation() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = Br_White(10, 10) + Dim g As Graphics = Graphics(bmp) + g.ScaleTransform 3, 3 + g.DrawLine Pen(Color(255, 255, 0, 0), 1, UnitPixel), 0, 1.5, 3, 1.5 + Expect.AreEqual "....#.....", Br_Column(bmp, 2, 0, 9), "rows painted by a pixel pen of width 1 under ScaleTransform 3, 3" + Expect.Done + End Sub + + [TestCase] + Public Sub New_Defaults_WidthOneAndSolid() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim linePen As Pen = Pen(Color(255, 255, 0, 0)) + Expect.AreEqual 1!, linePen.Width, "Width of a pen made without a width" + Expect.AreEqual CLng(PenTypeSolidColor), CLng(linePen.PenType), "PenType of a pen made from a colour" + Dim solid As SolidBrush = SolidBrush(Color(255, 0, 0, 255)) + Dim fromSolid As Pen = Pen(solid, 3) + Expect.AreEqual CLng(PenTypeSolidColor), CLng(fromSolid.PenType), "PenType of a pen made from a SolidBrush" + Expect.AreEqual 3!, fromSolid.Width, "Width of a pen made from a brush with width 3" + Expect.Done + End Sub + + [TestCase] + Public Sub Clone_IsIndependent() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim linePen As Pen = Pen(Color(255, 1, 2, 3), 5) + Dim copy As Pen = linePen.Clone() + copy.Width = 7 + Expect.AreEqual 5!, linePen.Width, "Width of the original after the copy was changed" + Expect.AreEqual 7!, copy.Width, "Width of the copy" + Dim c As Color = copy.Color + Expect.AreEqual "FF010203", Br_Hex(c.argb), "Color of the copy" + Expect.Done + End Sub + + ' ---- the brush of a pen + + [TestCase] + Public Sub Brush_ReturnsABrushOfTheSameClass() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim hatch As HatchBrush = New HatchBrush(HatchStyleCross, Color(255, 1, 2, 3), Color(255, 4, 5, 6)) + Dim linePen As Pen = Pen(hatch, 2) + Expect.AreEqual CLng(PenTypeHatchFill), CLng(linePen.PenType), "PenType of a pen made from a HatchBrush" + Dim b As Brush = linePen.Brush + Expect.IsFalse b Is Nothing, "Brush returns a brush" + If Not b Is Nothing Then Expect.AreEqual CLng(BrushTypeHatchFill), CLng(b.Type), "Type of the brush of a hatch pen" + Expect.Done + End Sub + + [TestCase] + Public Sub Color_NonSolidPen_ReadsZero_AndSettingMakesItSolid() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim hatch As HatchBrush = New HatchBrush(HatchStyleCross, Color(255, 1, 2, 3), Color(255, 4, 5, 6)) + Dim linePen As Pen = Pen(hatch, 2) + Dim c As Color = linePen.Color + Expect.AreEqual "00000000", Br_Hex(c.argb), "Color of a hatch pen" + linePen.Color = Color(255, 9, 9, 9) + c = linePen.Color + Expect.AreEqual CLng(PenTypeSolidColor), CLng(linePen.PenType), "PenType after Color was set" + Expect.AreEqual "FF090909", Br_Hex(c.argb), "Color after it was set" + Expect.Done + End Sub + + [TestCase] + Public Sub Brush_AfterEarlierFailure_ReturnsTheBrush() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim linePen As Pen = Pen(Color(255, 255, 0, 0), 2) + Dim m As Matrix = linePen.Transform + Expect.AreEqual CLng(InvalidParameter), CLng(linePen.LastResult), "LastResult after reading Transform" + Dim b As Brush = linePen.Brush + Expect.IsFalse b Is Nothing, "Brush of a pen that recorded an earlier failure returns the brush" + Expect.Done + End Sub + + [TestCase] + Public Sub CustomStartCap_AfterEarlierFailure_ReturnsTheCap() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim fill As GraphicsPath = New GraphicsPath() + fill.AddRectangle -1, -1, 2, 1 + Dim noStroke As GraphicsPath + Dim linePen As Pen = Pen(Color(255, 255, 0, 0), 2) + linePen.CustomStartCap = New CustomLineCap(fill, noStroke) + Dim m As Matrix = linePen.Transform + Expect.AreEqual CLng(InvalidParameter), CLng(linePen.LastResult), "LastResult after reading Transform" + Dim cap As CustomLineCap = linePen.CustomStartCap + Expect.IsFalse cap Is Nothing, "CustomStartCap of a pen that recorded an earlier failure returns the cap" + Expect.Done + End Sub + + ' ---- caps + + [TestCase] + Public Sub Caps_StartEndAndDashCaps_AreSetTogetherOrApart() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim linePen As Pen = Pen(Color(255, 255, 0, 0), 4) + Expect.AreEqual CLng(LineCapFlat), CLng(linePen.StartCap), "default StartCap" + Expect.AreEqual CLng(Ok), CLng(linePen.SetLineCap(LineCapSquare, LineCapDiamondAnchor, DashCapTriangle)), "status of SetLineCap" + Expect.AreEqual CLng(LineCapSquare), CLng(linePen.StartCap), "StartCap after SetLineCap" + Expect.AreEqual CLng(LineCapDiamondAnchor), CLng(linePen.EndCap), "EndCap after SetLineCap" + Expect.AreEqual CLng(DashCapTriangle), CLng(linePen.DashCap), "DashCap after SetLineCap" + linePen.Caps = LineCapRound + Expect.AreEqual CLng(LineCapRound), CLng(linePen.StartCap), "StartCap after Caps = LineCapRound" + Expect.AreEqual CLng(LineCapRound), CLng(linePen.EndCap), "EndCap after Caps = LineCapRound" + Expect.Done + End Sub + + [TestCase] + Public Sub StartCap_Round_IsDrawnRoundAndTheEndFlat() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = Br_White(16, 8) + Dim g As Graphics = Graphics(bmp) + Dim linePen As Pen = Pen(Color(255, 255, 0, 0), 4) + linePen.StartCap = LineCapRound + linePen.EndCap = LineCapFlat + g.DrawLine linePen, 5, 4, 11, 4 + Expect.AreEqual ".....######.....", Br_Row(bmp, 2, 0, 15), "row 2 of a line with a round start cap and a flat end cap" + Expect.AreEqual "....#######.....", Br_Row(bmp, 3, 0, 15), "row 3 of the line" + Expect.AreEqual "...########.....", Br_Row(bmp, 4, 0, 15), "row 4 of the line" + Expect.Done + End Sub + + [TestCase] + Public Sub CustomStartCap_CustomLineCap_ReturnsTheCap() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim fill As GraphicsPath = New GraphicsPath() + fill.AddRectangle -1, -1, 2, 1 + Dim noStroke As GraphicsPath + Dim cap As CustomLineCap = New CustomLineCap(fill, noStroke) + cap.BaseInset = 2 + Dim linePen As Pen = Pen(Color(255, 255, 0, 0), 2) + linePen.CustomStartCap = cap + Expect.AreEqual CLng(LineCapCustom), CLng(linePen.StartCap), "StartCap after a custom start cap was set" + Dim back As CustomLineCap = linePen.CustomStartCap + Expect.IsFalse back Is Nothing, "CustomStartCap returns the cap" + If Not back Is Nothing Then Expect.AreEqual 2!, back.BaseInset, "BaseInset of the cap CustomStartCap returns" + Expect.Done + End Sub + + [TestCase] + Public Sub CustomStartCap_ArrowCap_ReturnsAUsableCap() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim linePen As Pen = ArrowPen() + Dim cap As ArrowCap = linePen.CustomStartCap + Expect.IsFalse cap Is Nothing, "CustomStartCap of a pen with an arrow cap returns a cap" + If Not cap Is Nothing Then + Expect.AreEqual 3!, cap.Height, "Height of the arrow cap CustomStartCap returns" + Expect.AreEqual 4!, cap.Width, "Width of the arrow cap CustomStartCap returns" + Expect.AreEqual CLng(Ok), CLng(cap.LastResult), "LastResult of the arrow cap CustomStartCap returns" + End If + Expect.Done + End Sub + + [TestCase] + Public Sub CustomEndCap_Get_ReturnsTheCap() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim fill As GraphicsPath = New GraphicsPath() + fill.AddRectangle -1, -1, 2, 1 + Dim noStroke As GraphicsPath + Dim cap As CustomLineCap = New CustomLineCap(fill, noStroke) + cap.BaseInset = 2 + Dim linePen As Pen = Pen(Color(255, 255, 0, 0), 2) + linePen.CustomEndCap = cap + Expect.AreEqual CLng(LineCapCustom), CLng(linePen.EndCap), "EndCap after a custom end cap was set" + Dim back As CustomLineCap + On Error Resume Next + Set back = linePen.CustomEndCap + Dim errNumber As Long = Err.Number + On Error GoTo 0 + Expect.AreEqual 0, errNumber, "error raised by reading CustomEndCap" + Expect.IsFalse back Is Nothing, "CustomEndCap returns the cap" + Expect.Done + End Sub + + [TestCase] + Public Sub CustomEndCap_Get_WithACustomStartCap_ReturnsTheCap() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim fill As GraphicsPath = New GraphicsPath() + fill.AddRectangle -1, -1, 2, 1 + Dim noStroke As GraphicsPath + Dim startCap As CustomLineCap = New CustomLineCap(fill, noStroke) + Dim endCap As CustomLineCap = New CustomLineCap(fill, noStroke) + endCap.BaseInset = 3 + Dim linePen As Pen = Pen(Color(255, 255, 0, 0), 2) + linePen.CustomStartCap = startCap + linePen.CustomEndCap = endCap + Dim back As CustomLineCap + On Error Resume Next + Set back = linePen.CustomEndCap + Dim errNumber As Long = Err.Number + On Error GoTo 0 + Expect.AreEqual 0, errNumber, "error raised by reading CustomEndCap of a pen with a custom start cap and a custom end cap" + Expect.IsFalse back Is Nothing, "CustomEndCap returns the cap" + Expect.Done + End Sub + + ' ---- dashes and compound lines + + [TestCase] + Public Sub DashStyle_Dash_HasTwoLengths_AndCustomPatternSetsCustom() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim linePen As Pen = Pen(Color(255, 255, 0, 0), 2) + Expect.AreEqual 0, linePen.DashPatternCount, "DashPatternCount of a solid pen" + linePen.DashStyle = DashStyleDash + Expect.AreEqual 2, linePen.DashPatternCount, "DashPatternCount of DashStyleDash" + Dim pattern() As Single = linePen.DashPattern + Expect.AreEqual 1, LBound(pattern), "lower bound of DashPattern" + Expect.AreEqual 3!, pattern(1), "first length of DashStyleDash" + Expect.AreEqual 1!, pattern(2), "second length of DashStyleDash" + Dim custom(1 To 2) As Single + custom(1) = 3: custom(2) = 1 + linePen.DashPattern = custom + Expect.AreEqual CLng(DashStyleCustom), CLng(linePen.DashStyle), "DashStyle after a pattern was set" + Expect.Done + End Sub + + [TestCase] + Public Sub DashStyle_Dash_IsDrawnAsDashes() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = Br_White(40, 12) + Dim g As Graphics = Graphics(bmp) + Dim linePen As Pen = Pen(Color(255, 255, 0, 0), 2) + linePen.DashStyle = DashStyleDash + g.DrawLine linePen, 0, 6, 40, 6 + Expect.AreEqual "######..######..######..######..######..", Br_Row(bmp, 5, 0, 39), "row 5 of a DashStyleDash line" + Dim custom(1 To 2) As Single + custom(1) = 3: custom(2) = 1 + linePen.DashPattern = custom + linePen.DashOffset = 2 + g.Clear Color(255, 255, 255, 255) + g.DrawLine linePen, 0, 6, 40, 6 + Expect.AreEqual "##..######..######..######..######..####", Br_Row(bmp, 5, 0, 39), "row 5 of a line with the pattern 3, 1 and DashOffset 2" + Expect.Done + End Sub + + [TestCase] + Public Sub CompoundArray_DrawsParallelLines() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim thick As Pen = Pen(Color(255, 255, 0, 0), 8) + Expect.AreEqual 0, thick.CompoundArrayCount, "CompoundArrayCount of a new pen" + Dim bands(1 To 4) As Single + bands(1) = 0: bands(2) = 0.25: bands(3) = 0.75: bands(4) = 1 + thick.CompoundArray = bands + Expect.AreEqual 4, thick.CompoundArrayCount, "CompoundArrayCount after four values were set" + Dim bmp As Bitmap = Br_White(12, 12) + Dim g As Graphics = Graphics(bmp) + g.DrawLine thick, 0, 6, 12, 6 + Expect.AreEqual "..##....##..", Br_Column(bmp, 6, 0, 11), "column of a compound line made of two bands" + Expect.Done + End Sub + + ' ---- joins and alignment + + [TestCase] + Public Sub MiterLimit_BelowOneBecomesOne() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim linePen As Pen = Pen(Color(255, 255, 0, 0), 2) + Expect.AreEqual CLng(LineJoinMiter), CLng(linePen.LineJoin), "default LineJoin" + Expect.AreEqual 10!, linePen.MiterLimit, "default MiterLimit" + linePen.MiterLimit = 4 + Expect.AreEqual 4!, linePen.MiterLimit, "MiterLimit after it was set to 4" + linePen.MiterLimit = 0.5 + Expect.AreEqual 1!, linePen.MiterLimit, "MiterLimit after it was set to 0.5" + Expect.Done + End Sub + + [TestCase] + Public Sub Alignment_Inset_KeepsTheLineInsideTheShape() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = Br_White(14, 10) + Dim g As Graphics = Graphics(bmp) + Dim linePen As Pen = Pen(Color(255, 255, 0, 0), 2) + Expect.AreEqual CLng(PenAlignmentCenter), CLng(linePen.Alignment), "default Alignment" + g.DrawRectangle linePen, 4, 2, 6, 6 + Expect.AreEqual "...########...", Br_Row(bmp, 8, 0, 13), "row 8 of a rectangle drawn with a centred pen" + g.Clear Color(255, 255, 255, 255) + linePen.Alignment = PenAlignmentInset + g.DrawRectangle linePen, 4, 2, 6, 6 + Expect.AreEqual "....######....", Br_Row(bmp, 7, 0, 13), "row 7 of a rectangle drawn with an inset pen" + Expect.AreEqual "..............", Br_Row(bmp, 8, 0, 13), "row 8 of the same rectangle" + Expect.Done + End Sub + + ' ---- the transformation + + [TestCase] + Public Sub Transform_Get_ReturnsTheMatrix() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim tall As Pen = Pen(Color(255, 255, 0, 0), 2) + tall.TranslateTransform 3, 4 + Dim m As Matrix = tall.Transform + Expect.IsFalse m Is Nothing, "Transform returns a matrix" + If Not m Is Nothing Then Expect.AreEqual 3!, m.OffsetX, "OffsetX of Transform after TranslateTransform 3, 4" + Expect.AreEqual CLng(Ok), CLng(tall.LastResult), "LastResult after reading Transform" + Expect.Done + End Sub + + [TestCase] + Public Sub ScaleTransform_ThickensTheLine_AndResetTransformRestoresIt() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = Br_White(10, 20) + Dim g As Graphics = Graphics(bmp) + Dim tall As Pen = Pen(Color(255, 255, 0, 0), 2) + Expect.AreEqual CLng(Ok), CLng(tall.ScaleTransform(1, 3)), "status of ScaleTransform" + g.DrawLine tall, 0, 10, 10, 10 + Expect.AreEqual ".......######.......", Br_Column(bmp, 5, 0, 19), "column of a line drawn with a pen scaled by 1, 3" + tall.ResetTransform + g.Clear Color(255, 255, 255, 255) + g.DrawLine tall, 0, 10, 10, 10 + Expect.AreEqual ".........##.........", Br_Column(bmp, 5, 0, 19), "column of a line drawn after ResetTransform" + Expect.Done + End Sub + +End Module diff --git a/test/package-tests/GdiPlus/Sources/Fx_EffectTests.twin b/test/package-tests/GdiPlus/Sources/Fx_EffectTests.twin new file mode 100644 index 00000000..a3cf2d1b --- /dev/null +++ b/test/package-tests/GdiPlus/Sources/Fx_EffectTests.twin @@ -0,0 +1,534 @@ +' Fx_EffectTests: the image effects (Effect and its eleven classes) and their parameter types. +' The effects are applied to bitmaps of GdiPlus; the cases check what each one stores and returns. +[TestFixture] +Module Fx_EffectTests + + ' ---- Effect.Parameters: the getter without a size + + [TestCase] + Public Sub Blur_ParametersGetWithoutSize_ReturnsTheParameters() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim fx As Blur = Blur(5!, False) + Dim p As BlurParams = fx.Parameters + Expect.AreEqual 0, fx.LastResult, "Blur.LastResult after reading Parameters without a size" + Expect.AreEqual 5, p.radius, "Blur.Parameters.radius read without a size" + Expect.Done + End Sub + + [TestCase] + Public Sub Sharpen_ParametersGetWithoutSize_ReturnsTheParameters() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim fx As Sharpen = Sharpen(5!, 50!) + Dim p As SharpenParams = fx.Parameters + Expect.AreEqual 0, fx.LastResult, "Sharpen.LastResult after reading Parameters without a size" + Expect.AreEqual 5, p.radius, "Sharpen.Parameters.radius read without a size" + Expect.AreEqual 50, p.amount, "Sharpen.Parameters.amount read without a size" + Expect.Done + End Sub + + [TestCase] + Public Sub BrightnessContrast_ParametersGetWithoutSize_ReturnsTheParameters() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim fx As BrightnessContrast = BrightnessContrast(10, 20) + Dim p As BrightnessContrastParams = fx.Parameters + Expect.AreEqual 0, fx.LastResult, "BrightnessContrast.LastResult after reading Parameters without a size" + Expect.AreEqual 10, p.brightness, "BrightnessContrast.Parameters.brightness read without a size" + Expect.AreEqual 20, p.contrast, "BrightnessContrast.Parameters.contrast read without a size" + Expect.Done + End Sub + + [TestCase] + Public Sub HueSaturationLightness_ParametersGetWithoutSize_ReturnsTheParameters() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim fx As HueSaturationLightness = HueSaturationLightness(120, -20, 10) + Dim p As HueSaturationLightnessParams = fx.Parameters + Expect.AreEqual 0, fx.LastResult, "HueSaturationLightness.LastResult after reading Parameters without a size" + Expect.AreEqual 120, p.hue, "HueSaturationLightness.Parameters.hue read without a size" + Expect.AreEqual -20, p.saturation, "HueSaturationLightness.Parameters.saturation read without a size" + Expect.AreEqual 10, p.lightness, "HueSaturationLightness.Parameters.lightness read without a size" + Expect.Done + End Sub + + [TestCase] + Public Sub Levels_ParametersGetWithoutSize_ReturnsTheParameters() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim fx As Levels = Levels(80, 50, 20) + Dim p As LevelsParams = fx.Parameters + Expect.AreEqual 0, fx.LastResult, "Levels.LastResult after reading Parameters without a size" + Expect.AreEqual 80, p.highight, "Levels.Parameters.highight read without a size" + Expect.AreEqual 50, p.midtone, "Levels.Parameters.midtone read without a size" + Expect.AreEqual 20, p.shadow, "Levels.Parameters.shadow read without a size" + Expect.Done + End Sub + + [TestCase] + Public Sub Tint_ParametersGetWithoutSize_ReturnsTheParameters() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim fx As Tint = Tint(100, 50) + Dim p As TintParams = fx.Parameters + Expect.AreEqual 0, fx.LastResult, "Tint.LastResult after reading Parameters without a size" + Expect.AreEqual 100, p.hue, "Tint.Parameters.hue read without a size" + Expect.AreEqual 50, p.amount, "Tint.Parameters.amount read without a size" + Expect.Done + End Sub + + [TestCase] + Public Sub ColorBalance_ParametersGetWithoutSize_ReturnsTheParameters() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim fx As ColorBalance = ColorBalance(10, -20, 30) + Dim p As ColorBalanceParams = fx.Parameters + Expect.AreEqual 0, fx.LastResult, "ColorBalance.LastResult after reading Parameters without a size" + Expect.AreEqual 10, p.cyanRed, "ColorBalance.Parameters.cyanRed read without a size" + Expect.AreEqual -20, p.magentaGreen, "ColorBalance.Parameters.magentaGreen read without a size" + Expect.AreEqual 30, p.yellowBlue, "ColorBalance.Parameters.yellowBlue read without a size" + Expect.Done + End Sub + + [TestCase] + Public Sub ColorCurve_ParametersGetWithoutSize_ReturnsTheParameters() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim fx As ColorCurve = ColorCurve(AdjustContrast, CurveChannelRed, 20) + Dim p As ColorCurveParams = fx.Parameters + Expect.AreEqual 0, fx.LastResult, "ColorCurve.LastResult after reading Parameters without a size" + Expect.AreEqual 2, p.adjustment, "ColorCurve.Parameters.adjustment read without a size" + Expect.AreEqual 1, p.channel, "ColorCurve.Parameters.channel read without a size" + Expect.AreEqual 20, p.value, "ColorCurve.Parameters.value read without a size" + Expect.Done + End Sub + + [TestCase] + Public Sub ColorMatrixEffect_ParametersGetWithoutSize_ReturnsTheParameters() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim cm As ColorMatrix = Fx_Identity() + cm.m(2, 0) = 1 + Dim fx As ColorMatrixEffect = ColorMatrixEffect() + fx.Parameters = cm + Dim back As ColorMatrix = fx.Parameters + Expect.AreEqual 0, fx.LastResult, "ColorMatrixEffect.LastResult after reading Parameters without a size" + Expect.AreEqual 1, back.m(2, 0), "ColorMatrixEffect.Parameters.m(2, 0) read without a size" + Expect.AreEqual 1, back.m(1, 1), "ColorMatrixEffect.Parameters.m(1, 1) read without a size" + Expect.Done + End Sub + + [TestCase] + Public Sub ColorLUT_ParametersGetWithoutSize_ReturnsTheParameters() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim tables As ColorLUTParams + Dim i As Long + For i = 0 To 255 + tables.lutB.b(i) = 255 - i + tables.lutG.b(i) = i + tables.lutR.b(i) = i + tables.lutA.b(i) = i + Next + Dim fx As ColorLUT = ColorLUT() + fx.Parameters = tables + Dim p As ColorLUTParams = fx.Parameters + Expect.AreEqual 0, fx.LastResult, "ColorLUT.LastResult after reading Parameters without a size" + Expect.AreEqual 245, p.lutB.b(10), "ColorLUT.Parameters.lutB.b(10) read without a size" + Expect.AreEqual 10, p.lutG.b(10), "ColorLUT.Parameters.lutG.b(10) read without a size" + Expect.Done + End Sub + + [TestCase] + Public Sub RedEyeCorrection_ParametersGetWithoutSize_SetsNoError() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim fx As RedEyeCorrection = RedEyeCorrection() + Dim p As RedEyeCorrectionParams = fx.Parameters + Expect.AreEqual 0, fx.LastResult, "RedEyeCorrection.LastResult after reading Parameters without a size" + Expect.Done + End Sub + + ' ---- Effect.ParameterSize + + [TestCase] + Public Sub ParameterSize_BrightnessContrast_IsTheSizeOfItsParameters() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim fx As BrightnessContrast = BrightnessContrast(10, 20) + Expect.AreEqual LenB(Of BrightnessContrastParams), fx.ParameterSize, "BrightnessContrast.ParameterSize" + Expect.Done + End Sub + + [TestCase] + Public Sub ParameterSize_ColorMatrixEffect_IsTheSizeOfItsParameters() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim fx As ColorMatrixEffect = ColorMatrixEffect() + Expect.AreEqual LenB(Of ColorMatrix), fx.ParameterSize, "ColorMatrixEffect.ParameterSize" + Expect.Done + End Sub + + ' ---- the constructors that take a parameters structure + + [TestCase] + Public Sub ColorBalance_NewWithParams_StoresTheParameters() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim fx As ColorBalance = ColorBalance(ColorBalanceParams(10, -20, 30)) + Expect.AreEqual 0, fx.LastResult, "ColorBalance.LastResult after ColorBalance(params)" + Dim p As ColorBalanceParams = fx.Parameters(LenB(Of ColorBalanceParams)) + Expect.AreEqual 10, p.cyanRed, "cyanRed after ColorBalance(params)" + Expect.AreEqual -20, p.magentaGreen, "magentaGreen after ColorBalance(params)" + Expect.AreEqual 30, p.yellowBlue, "yellowBlue after ColorBalance(params)" + Expect.Done + End Sub + + [TestCase] + Public Sub ColorCurve_NewWithParams_StoresTheParameters() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim fx As ColorCurve = ColorCurve(ColorCurveParams(AdjustContrast, CurveChannelRed, 20)) + Expect.AreEqual 0, fx.LastResult, "ColorCurve.LastResult after ColorCurve(params)" + Dim p As ColorCurveParams = fx.Parameters(LenB(Of ColorCurveParams)) + Expect.AreEqual 2, p.adjustment, "adjustment after ColorCurve(params)" + Expect.AreEqual 1, p.channel, "channel after ColorCurve(params)" + Expect.AreEqual 20, p.value, "value after ColorCurve(params)" + Expect.Done + End Sub + + [TestCase] + Public Sub ColorLUT_NewWithParams_StoresTheParameters() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim tables As ColorLUTParams + Dim i As Long + For i = 0 To 255 + tables.lutB.b(i) = 255 - i + tables.lutG.b(i) = i + tables.lutR.b(i) = i + tables.lutA.b(i) = i + Next + Dim fx As ColorLUT = ColorLUT(tables) + Expect.AreEqual 0, fx.LastResult, "ColorLUT.LastResult after ColorLUT(params)" + Dim p As ColorLUTParams = fx.Parameters(LenB(Of ColorLUTParams)) + Expect.AreEqual 245, p.lutB.b(10), "lutB.b(10) after ColorLUT(params)" + Expect.AreEqual 10, p.lutA.b(10), "lutA.b(10) after ColorLUT(params)" + Expect.Done + End Sub + + [TestCase] + Public Sub ColorMatrixEffect_NewWithParams_StoresTheMatrix() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim cm As ColorMatrix = Fx_Identity() + cm.m(2, 0) = 1 + Dim fx As ColorMatrixEffect = ColorMatrixEffect(cm) + Expect.AreEqual 0, fx.LastResult, "ColorMatrixEffect.LastResult after ColorMatrixEffect(params)" + Dim back As ColorMatrix = fx.Parameters(LenB(Of ColorMatrix)) + Expect.AreEqual 1, back.m(2, 0), "m(2, 0) after ColorMatrixEffect(params)" + Expect.AreEqual 1, back.m(1, 1), "m(1, 1) after ColorMatrixEffect(params)" + Expect.Done + End Sub + + [TestCase] + Public Sub HueSaturationLightness_NewWithParams_StoresTheParameters() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim fx As HueSaturationLightness = HueSaturationLightness(HueSaturationLightnessParams(120, -20, 10)) + Expect.AreEqual 0, fx.LastResult, "HueSaturationLightness.LastResult after HueSaturationLightness(params)" + Dim p As HueSaturationLightnessParams = fx.Parameters(LenB(Of HueSaturationLightnessParams)) + Expect.AreEqual 120, p.hue, "hue after HueSaturationLightness(params)" + Expect.AreEqual -20, p.saturation, "saturation after HueSaturationLightness(params)" + Expect.AreEqual 10, p.lightness, "lightness after HueSaturationLightness(params)" + Expect.Done + End Sub + + [TestCase] + Public Sub Levels_NewWithParams_StoresTheParameters() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim fx As Levels = Levels(LevelsParams(80, 50, 20)) + Expect.AreEqual 0, fx.LastResult, "Levels.LastResult after Levels(params)" + Dim p As LevelsParams = fx.Parameters(LenB(Of LevelsParams)) + Expect.AreEqual 80, p.highight, "highlight level after Levels(params)" + Expect.AreEqual 50, p.midtone, "midtone after Levels(params)" + Expect.AreEqual 20, p.shadow, "shadow after Levels(params)" + Expect.Done + End Sub + + ' ---- the constructors that take the individual values + + [TestCase] + Public Sub ColorBalance_NewWithValues_StoresTheParameters() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim fx As ColorBalance = ColorBalance(10, -20, 30) + Expect.AreEqual 0, fx.LastResult, "ColorBalance.LastResult after ColorBalance(10, -20, 30)" + Dim p As ColorBalanceParams = fx.Parameters(LenB(Of ColorBalanceParams)) + Expect.AreEqual 10, p.cyanRed, "cyanRed after ColorBalance(10, -20, 30)" + Expect.AreEqual -20, p.magentaGreen, "magentaGreen after ColorBalance(10, -20, 30)" + Expect.AreEqual 30, p.yellowBlue, "yellowBlue after ColorBalance(10, -20, 30)" + Expect.Done + End Sub + + [TestCase] + Public Sub ColorCurve_NewWithValues_StoresTheParameters() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim fx As ColorCurve = ColorCurve(AdjustContrast, CurveChannelRed, 20) + Expect.AreEqual 0, fx.LastResult, "ColorCurve.LastResult after ColorCurve(AdjustContrast, CurveChannelRed, 20)" + Dim p As ColorCurveParams = fx.Parameters(LenB(Of ColorCurveParams)) + Expect.AreEqual 2, p.adjustment, "adjustment after the three-value form" + Expect.AreEqual 1, p.channel, "channel after the three-value form" + Expect.AreEqual 20, p.value, "value after the three-value form" + Expect.Done + End Sub + + [TestCase] + Public Sub HueSaturationLightness_NewWithValues_StoresTheParameters() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim fx As HueSaturationLightness = HueSaturationLightness(120, -20, 10) + Expect.AreEqual 0, fx.LastResult, "HueSaturationLightness.LastResult after HueSaturationLightness(120, -20, 10)" + Dim p As HueSaturationLightnessParams = fx.Parameters(LenB(Of HueSaturationLightnessParams)) + Expect.AreEqual 120, p.hue, "hue after the three-value form" + Expect.AreEqual -20, p.saturation, "saturation after the three-value form" + Expect.AreEqual 10, p.lightness, "lightness after the three-value form" + Expect.Done + End Sub + + [TestCase] + Public Sub Levels_NewWithValues_StoresTheParameters() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim fx As Levels = Levels(80, 50, 20) + Expect.AreEqual 0, fx.LastResult, "Levels.LastResult after Levels(80, 50, 20)" + Dim p As LevelsParams = fx.Parameters(LenB(Of LevelsParams)) + Expect.AreEqual 80, p.highight, "highlight level after the three-value form" + Expect.AreEqual 50, p.midtone, "midtone after the three-value form" + Expect.AreEqual 20, p.shadow, "shadow after the three-value form" + Expect.Done + End Sub + + ' ---- ColorLUT: the four-array form and ColorLUTParams( ) + + [TestCase] + Public Sub ColorLUT_NewWithFourArrays_TakesTheAlphaTableFromLutA() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim lutB(0 To 255) As Byte, lutG(0 To 255) As Byte, lutR(0 To 255) As Byte, lutA(0 To 255) As Byte + Dim i As Long + For i = 0 To 255 + lutB(i) = 255 - i + lutG(i) = i + lutR(i) = i + lutA(i) = i + Next + Dim fx As ColorLUT = ColorLUT(lutB, lutG, lutR, lutA) + Expect.AreEqual 0, fx.LastResult, "ColorLUT.LastResult after the four-array form" + Dim p As ColorLUTParams = fx.Parameters(LenB(Of ColorLUTParams)) + Expect.AreEqual 245, p.lutB.b(10), "lutB.b(10) after the four-array form" + Expect.AreEqual 10, p.lutG.b(10), "lutG.b(10) after the four-array form" + Expect.AreEqual 10, p.lutR.b(10), "lutR.b(10) after the four-array form" + Expect.AreEqual 10, p.lutA.b(10), "lutA.b(10) after the four-array form" + Expect.Done + End Sub + + [TestCase] + Public Sub ColorLUTParams_FromFourArrays_TakesTheAlphaTableFromLutA() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim lutB(0 To 255) As Byte, lutG(0 To 255) As Byte, lutR(0 To 255) As Byte, lutA(0 To 255) As Byte + Dim i As Long + For i = 0 To 255 + lutB(i) = 255 - i + lutG(i) = i + lutR(i) = i + lutA(i) = i + Next + Dim p As ColorLUTParams = ColorLUTParams(lutB, lutG, lutR, lutA) + Expect.AreEqual 245, p.lutB.b(10), "lutB.b(10) of ColorLUTParams(...)" + Expect.AreEqual 10, p.lutG.b(10), "lutG.b(10) of ColorLUTParams(...)" + Expect.AreEqual 10, p.lutR.b(10), "lutR.b(10) of ColorLUTParams(...)" + Expect.AreEqual 10, p.lutA.b(10), "lutA.b(10) of ColorLUTParams(...)" + Expect.Done + End Sub + + [TestCase] + Public Sub ColorLUT_ApplyEffectWithFourArrays_KeepsAlphaWithTheIdentityTable() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim lutB(0 To 255) As Byte, lutG(0 To 255) As Byte, lutR(0 To 255) As Byte, lutA(0 To 255) As Byte + Dim i As Long + For i = 0 To 255 + lutB(i) = 255 - i + lutG(i) = i + lutR(i) = i + lutA(i) = i + Next + Dim bmp As Bitmap = Fx_Flat(200, 100, 50) + Expect.AreEqual 0, bmp.ApplyEffect(ColorLUT(lutB, lutG, lutR, lutA), GpRect(0, 0, 4, 4)), "ApplyEffect with the four-array ColorLUT" + Expect.AreEqual "FFC864CD", Fx_Px(bmp), "pixel after inverting blue and keeping alpha, red and green" + Expect.Done + End Sub + + ' ---- RedEyeCorrection + + [TestCase] + Public Sub RedEyeCorrection_NewWithParams_StoresTheAreas() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim p As RedEyeCorrectionParams + Expect.AreEqual 0, p.Allocate(2), "Allocate(2)" + Set p.Area(1) = GpRect(0, 0, 5, 5) + Set p.Area(2) = GpRect(5, 5, 4, 4) + Dim fx As RedEyeCorrection = RedEyeCorrection(p) + Expect.AreEqual 0, fx.LastResult, "RedEyeCorrection.LastResult after RedEyeCorrection(params)" + Dim bmp As Bitmap = Fx_FlatSized(10, 220, 20, 20) + Expect.AreEqual 0, bmp.ApplyEffect(fx, GpRect(0, 0, 10, 10)), "ApplyEffect with a RedEyeCorrection made from parameters" + p.Free + Expect.Done + End Sub + + [TestCase] + Public Sub RedEyeCorrection_NewKeywordWithParams_StoresTheAreas() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim p As RedEyeCorrectionParams + Expect.AreEqual 0, p.Allocate(1), "Allocate(1)" + Set p.Area(1) = GpRect(0, 0, 10, 10) + Dim fx As RedEyeCorrection = New RedEyeCorrection(p) + Expect.AreEqual 0, fx.LastResult, "RedEyeCorrection.LastResult after New RedEyeCorrection(params)" + Dim bmp As Bitmap = Fx_FlatSized(10, 220, 20, 20) + Expect.AreEqual 0, bmp.ApplyEffect(fx, GpRect(0, 0, 10, 10)), "ApplyEffect with a RedEyeCorrection made from parameters" + p.Free + Expect.Done + End Sub + + [TestCase] + Public Sub RedEyeCorrection_ParametersGet_AfterSettingAreas_ReturnsTheAreaCount() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim p As RedEyeCorrectionParams + Expect.AreEqual 0, p.Allocate(2), "Allocate(2)" + Set p.Area(1) = GpRect(0, 0, 5, 5) + Set p.Area(2) = GpRect(5, 5, 4, 4) + Dim fx As RedEyeCorrection = RedEyeCorrection() + fx.Parameters = p + fx.ClearResult + Dim back As RedEyeCorrectionParams = fx.Parameters(LenB(Of RedEyeCorrectionParams)) + Expect.AreEqual 0, fx.LastResult, "RedEyeCorrection.LastResult after reading Parameters with a size" + Expect.AreEqual 2, back.numberOfAreas, "numberOfAreas read back" + p.Free + Expect.Done + End Sub + + [TestCase] + Public Sub RedEyeCorrection_ApplyEffectWithoutAreas_ReturnsInvalidParameter() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim fx As RedEyeCorrection = RedEyeCorrection() + Expect.AreEqual Ok, fx.LastResult, "RedEyeCorrection.LastResult after RedEyeCorrection()" + Dim bmp As Bitmap = Fx_FlatSized(10, 220, 20, 20) + Expect.AreEqual InvalidParameter, bmp.ApplyEffect(fx, GpRect(0, 0, 10, 10)), "ApplyEffect with a RedEyeCorrection that has no areas" + Expect.Done + End Sub + + [TestCase] + Public Sub RedEyeCorrection_ParametersLet_StoresTheAreas() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim p As RedEyeCorrectionParams + Expect.AreEqual 0, p.Allocate(2), "Allocate(2)" + Set p.Area(1) = GpRect(0, 0, 5, 5) + Set p.Area(2) = GpRect(5, 5, 4, 4) + Dim fx As RedEyeCorrection = RedEyeCorrection() + fx.Parameters = p + Expect.AreEqual 0, fx.LastResult, "RedEyeCorrection.LastResult after setting Parameters" + Dim bmp As Bitmap = Fx_FlatSized(10, 220, 20, 20) + Expect.AreEqual 0, bmp.ApplyEffect(fx, GpRect(0, 0, 10, 10)), "ApplyEffect after setting the areas" + p.Free + Expect.Done + End Sub + + [TestCase] + Public Sub RedEyeCorrectionParams_FromArray_CopiesTheAreas() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim areas(1 To 2) As GpRect + areas(1) = GpRect(0, 0, 5, 5) + areas(2) = GpRect(5, 5, 4, 4) + Dim p As RedEyeCorrectionParams = RedEyeCorrectionParams(areas) + Expect.AreEqual 2, p.numberOfAreas, "numberOfAreas of RedEyeCorrectionParams(areas)" + Expect.AreEqual 5, p.Area(1).Width, "Area(1).Width of RedEyeCorrectionParams(areas)" + Expect.AreEqual 5, p.Area(2).X, "Area(2).X of RedEyeCorrectionParams(areas)" + p.Free + Expect.Done + End Sub + + ' ---- ColorMatrix: the output component is the first index + + [TestCase] + Public Sub ColorMatrix_FirstIndexIsTheOutputComponent_ImageAttributes() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim src As Bitmap = Bitmap(4!, 4!) + src.Pixel(1, 1) = Color(255, 200, 100, 40) + Dim dst As Bitmap = Bitmap(4!, 4!) + Dim g As Graphics = Graphics(dst) + Dim ia As ImageAttributes = New ImageAttributes() + Dim cm As ColorMatrix = Fx_Identity() + cm.m(2, 2) = 0 + cm.m(2, 0) = 1 ' blue = red + ia.SetColorMatrix cm + g.DrawImage src, GpRect(0, 0, 4, 4), GpRect(0, 0, 4, 4), UnitPixel, ia + Expect.AreEqual "FFC864C8", Fx_Px(dst), "m(2, 0) = 1 makes blue from red" + + Dim cm2 As ColorMatrix = Fx_Identity() + cm2.m(0, 0) = 0 + cm2.m(0, 2) = 1 ' red = blue + ia.SetColorMatrix cm2 + g.Clear Color(0, 0, 0, 0) + g.DrawImage src, GpRect(0, 0, 4, 4), GpRect(0, 0, 4, 4), UnitPixel, ia + Expect.AreEqual "FF286428", Fx_Px(dst), "m(0, 2) = 1 makes red from blue" + Expect.Done + End Sub + + [TestCase] + Public Sub ColorMatrix_FirstIndexIsTheOutputComponent_ColorMatrixEffect() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim cm As ColorMatrix = Fx_Identity() + cm.m(2, 2) = 0 + cm.m(2, 0) = 1 ' blue = red + Dim fx As ColorMatrixEffect = ColorMatrixEffect() + fx.Parameters = cm + Dim bmp As Bitmap = Fx_Flat(200, 100, 40) + Expect.AreEqual 0, bmp.ApplyEffect(fx, GpRect(0, 0, 4, 4)), "ApplyEffect with m(2, 0) = 1" + Expect.AreEqual "FFC864C8", Fx_Px(bmp), "m(2, 0) = 1 makes blue from red" + + Dim cm2 As ColorMatrix = Fx_Identity() + cm2.m(0, 0) = 0 + cm2.m(0, 2) = 1 ' red = blue + fx.Parameters = cm2 + Dim bmp2 As Bitmap = Fx_Flat(200, 100, 40) + Expect.AreEqual 0, bmp2.ApplyEffect(fx, GpRect(0, 0, 4, 4)), "ApplyEffect with m(0, 2) = 1" + Expect.AreEqual "FF286428", Fx_Px(bmp2), "m(0, 2) = 1 makes red from blue" + Expect.Done + End Sub + + ' ---- LevelsParams: the first field + + [TestCase] + Public Sub LevelsParams_HighightField_HoldsTheHighlightLevel() + Expect.Begin + Dim p As LevelsParams = LevelsParams(90, 10, 5) + Expect.AreEqual 90, p.highight, "LevelsParams(90, 10, 5).highight" + Expect.AreEqual 10, p.midtone, "LevelsParams(90, 10, 5).midtone" + Expect.AreEqual 5, p.shadow, "LevelsParams(90, 10, 5).shadow" + Expect.Done + End Sub + +End Module diff --git a/test/package-tests/GdiPlus/Sources/Fx_FontCollectionTests.twin b/test/package-tests/GdiPlus/Sources/Fx_FontCollectionTests.twin new file mode 100644 index 00000000..f315198a --- /dev/null +++ b/test/package-tests/GdiPlus/Sources/Fx_FontCollectionTests.twin @@ -0,0 +1,173 @@ +' Fx_FontCollectionTests: InstalledFontCollection, PrivateFontCollection and the members of +' FontCollection. The private collection loads arial.ttf from the Windows fonts folder, which it +' only reads. +[TestFixture] +Module Fx_FontCollectionTests + + Private Function Fx_ArialFile() As String + Fx_ArialFile = Environ$("SystemRoot") & "\Fonts\arial.ttf" + End Function + + ' ---- InstalledFontCollection + + [TestCase] + Public Sub InstalledFontCollection_Create_ReturnsACollection() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim fonts As InstalledFontCollection + On Error Resume Next + Set fonts = InstalledFontCollection() + Dim n As Long = Err.Number + Dim d As String = Err.Description + On Error GoTo 0 + Expect.AreEqual 0, n, "Err.Number after InstalledFontCollection() (" & d & ")" + Expect.IsFalse fonts Is Nothing, "InstalledFontCollection() returns an object" + Expect.Done + End Sub + + [TestCase] + Public Sub InstalledFontCollection_New_ReturnsACollection() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim fonts As InstalledFontCollection + On Error Resume Next + Set fonts = New InstalledFontCollection + Dim n As Long = Err.Number + Dim d As String = Err.Description + On Error GoTo 0 + Expect.AreEqual 0, n, "Err.Number after New InstalledFontCollection (" & d & ")" + Expect.IsFalse fonts Is Nothing, "New InstalledFontCollection returns an object" + Expect.Done + End Sub + + [TestCase] + Public Sub InstalledFontCollection_FamilyCount_CountsTheInstalledFamilies() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim fonts As InstalledFontCollection + On Error Resume Next + Set fonts = InstalledFontCollection() + On Error GoTo 0 + If fonts Is Nothing Then + Expect.Fail "InstalledFontCollection() returned Nothing" + Else + Expect.IsTrue fonts.FamilyCount > 0, "FamilyCount of the installed fonts is positive" + Expect.AreEqual Ok, fonts.LastResult, "LastResult after FamilyCount" + End If + Expect.Done + End Sub + + [TestCase] + Public Sub InstalledFontCollection_Families_IsOneBasedAndHoldsAnArialFamily() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim fonts As InstalledFontCollection + On Error Resume Next + Set fonts = InstalledFontCollection() + On Error GoTo 0 + If fonts Is Nothing Then + Expect.Fail "InstalledFontCollection() returned Nothing" + Else + Dim families() As FontFamily = fonts.Families + Expect.AreEqual 1, LBound(families), "lower bound of Families" + Expect.AreEqual fonts.FamilyCount, UBound(families), "upper bound of Families" + Dim i As Long, found As Boolean + For i = LBound(families) To UBound(families) + If Replace(families(i).FamilyName, vbNullChar, "") = "Arial" Then found = True + Next + Expect.IsTrue found, "Families holds the family Arial" + End If + Expect.Done + End Sub + + ' ---- PrivateFontCollection + + [TestCase] + Public Sub PrivateFontCollection_Create_ReturnsAnEmptyCollection() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim fonts As PrivateFontCollection + On Error Resume Next + Set fonts = PrivateFontCollection() + Dim n As Long = Err.Number + Dim d As String = Err.Description + On Error GoTo 0 + Expect.AreEqual 0, n, "Err.Number after PrivateFontCollection() (" & d & ")" + Expect.IsFalse fonts Is Nothing, "PrivateFontCollection() returns an object" + If Not fonts Is Nothing Then Expect.AreEqual 0, fonts.FamilyCount, "FamilyCount of a new private collection" + Expect.Done + End Sub + + [TestCase] + Public Sub PrivateFontCollection_New_ReturnsACollection() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim fonts As PrivateFontCollection + On Error Resume Next + Set fonts = New PrivateFontCollection + Dim n As Long = Err.Number + Dim d As String = Err.Description + On Error GoTo 0 + Expect.AreEqual 0, n, "Err.Number after New PrivateFontCollection (" & d & ")" + Expect.IsFalse fonts Is Nothing, "New PrivateFontCollection returns an object" + Expect.Done + End Sub + + [TestCase] + Public Sub PrivateFontCollection_AddFontFile_AddsTheFamilyOfTheFile() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim fonts As PrivateFontCollection + On Error Resume Next + Set fonts = PrivateFontCollection() + On Error GoTo 0 + If fonts Is Nothing Then + Expect.Fail "PrivateFontCollection() returned Nothing" + Else + Expect.AreEqual Ok, fonts.AddFontFile(Fx_ArialFile()), "AddFontFile of arial.ttf" + Expect.AreEqual 1, fonts.FamilyCount, "FamilyCount after adding arial.ttf" + If fonts.FamilyCount = 1 Then + Dim families() As FontFamily = fonts.Families + Expect.AreEqual "Arial", Replace(families(1).FamilyName, vbNullChar, ""), "the family added from arial.ttf" + End If + End If + Expect.Done + End Sub + + [TestCase] + Public Sub PrivateFontCollection_AddFontFile_MissingFile_ReturnsFileNotFound() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim fonts As PrivateFontCollection + On Error Resume Next + Set fonts = PrivateFontCollection() + On Error GoTo 0 + If fonts Is Nothing Then + Expect.Fail "PrivateFontCollection() returned Nothing" + Else + Expect.AreEqual FileNotFound, fonts.AddFontFile(Fx_Folder() & "\no such font.ttf"), "AddFontFile of a missing file" + End If + Expect.Done + End Sub + + [TestCase] + Public Sub PrivateFontCollection_FontFromTheCollection_IsAvailable() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim fonts As PrivateFontCollection + On Error Resume Next + Set fonts = PrivateFontCollection() + On Error GoTo 0 + If fonts Is Nothing Then + Expect.Fail "PrivateFontCollection() returned Nothing" + Else + fonts.AddFontFile Fx_ArialFile() + Dim ff As FontFamily = FontFamily("Arial", fonts) + Expect.IsTrue ff.IsAvailable, "FontFamily(""Arial"", collection).IsAvailable" + Dim f As GdiPlus.Font = Font("Arial", 14!, FontStyleRegular, UnitPoint, fonts) + Expect.IsTrue f.IsAvailable, "Font(""Arial"", 14, Regular, Point, collection).IsAvailable" + End If + Expect.Done + End Sub + +End Module diff --git a/test/package-tests/GdiPlus/Sources/Fx_FontTests.twin b/test/package-tests/GdiPlus/Sources/Fx_FontTests.twin new file mode 100644 index 00000000..948bbe7e --- /dev/null +++ b/test/package-tests/GdiPlus/Sources/Fx_FontTests.twin @@ -0,0 +1,167 @@ +' Fx_FontTests: Font and FontFamily. +' A family name is "Arial", which every Windows installation has. +[TestFixture] +Module Fx_FontTests + + ' ---- FontFamily.FamilyName + + [TestCase] + Public Sub FamilyName_OfAnInstalledFamily_EqualsTheNameGiven() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim ff As FontFamily = FontFamily("Arial") + Expect.IsTrue ff.IsAvailable, "FontFamily(""Arial"").IsAvailable" + Expect.AreEqual "Arial", ff.FamilyName, "FontFamily(""Arial"").FamilyName" + Expect.Done + End Sub + + [TestCase] + Public Sub FamilyName_OfAGenericFamily_HasNoNulCharacter() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim name As String = GenericSansSerif().FamilyName + Expect.IsTrue Len(name) > 0, "GenericSansSerif().FamilyName is not empty" + Expect.AreEqual 0, InStr(name, vbNullChar), "position of a NUL character in GenericSansSerif().FamilyName" + Expect.Done + End Sub + + [TestCase] + Public Sub FamilyName_WithTheNulRemoved_IsFoundByFontFamily() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim name As String = Replace(GenericSansSerif().FamilyName, vbNullChar, "") + Expect.IsTrue FontFamily(name).IsAvailable, "FontFamily(""" & name & """).IsAvailable" + Expect.Done + End Sub + + [TestCase] + Public Sub FamilyName_WithTheNul_IsFoundByFontFamilyAndFont() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim name As String = GenericSansSerif().FamilyName + Expect.IsTrue FontFamily(name).IsAvailable, "FontFamily(FamilyName with its NUL).IsAvailable" + Dim f As GdiPlus.Font = Font(name, 12!) + Expect.IsTrue f.IsAvailable, "Font(FamilyName with its NUL, 12).IsAvailable" + Expect.Done + End Sub + + ' ---- FontFamily + + [TestCase] + Public Sub FontFamily_UnknownName_IsNotAvailable() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim missing As FontFamily = FontFamily("No such family") + Expect.IsFalse missing.IsAvailable, "IsAvailable of an unknown family" + Expect.AreEqual FontFamilyNotFound, missing.LastResult, "LastResult after creating an unknown family" + Expect.IsFalse missing.IsBoldAvailable, "IsBoldAvailable of an unknown family" + Expect.AreEqual InvalidParameter, missing.LastResult, "LastResult after a member of an unknown family" + Expect.Done + End Sub + + [TestCase] + Public Sub GenericFamilies_AreCreatedOnceAndAreAvailable() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim a As FontFamily = GenericSansSerif() + Expect.IsTrue a.IsAvailable, "GenericSansSerif().IsAvailable" + Expect.IsTrue a Is GenericSansSerif(), "GenericSansSerif() returns the same object each time" + Expect.IsFalse GenericSerif() Is GenericSansSerif(), "GenericSerif() is another object than GenericSansSerif()" + Expect.IsTrue GenericMonospace().IsAvailable, "GenericMonospace().IsAvailable" + Expect.Done + End Sub + + [TestCase] + Public Sub Font_AscentAndLineSpacing_AreScaledFromTheDesignMetrics() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim ff As FontFamily = GenericSansSerif() + Dim em As Long = ff.EmHeight(FontStyleRegular) + Dim f As GdiPlus.Font = Font(ff, 20!, FontStyleRegular, UnitPixel) + Expect.IsTrue em > 0, "EmHeight is positive" + Expect.IsTrue Abs(f.Ascent - 20! * ff.CellAscent(FontStyleRegular) / em) < 0.01, "Ascent = Size * CellAscent / EmHeight" + Expect.IsTrue Abs(f.Descent - 20! * ff.CellDescent(FontStyleRegular) / em) < 0.01, "Descent = Size * CellDescent / EmHeight" + Expect.IsTrue Abs(f.LineSpacing - 20! * ff.LineSpacing(FontStyleRegular) / em) < 0.01, "LineSpacing = Size * LineSpacing / EmHeight" + Expect.Done + End Sub + + ' ---- Font + + [TestCase] + Public Sub Font_FromAFamilyName_KeepsSizeStyleAndUnit() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim f As GdiPlus.Font = Font("Arial", 12!, FontStyleBold, UnitPixel) + Expect.IsTrue f.IsAvailable, "IsAvailable" + Expect.AreEqual 12, f.Size, "Size" + Expect.AreEqual FontStyleBold, f.Style, "Style" + Expect.AreEqual UnitPixel, f.Unit, "Unit" + Expect.Done + End Sub + + [TestCase] + Public Sub Font_UnknownFamilyName_FallsBackToGenericSansSerif() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim f As GdiPlus.Font = Font("No such family", 20, FontStyleItalic, UnitPixel) + Expect.IsTrue f.IsAvailable, "IsAvailable of a font of an unknown family name" + Expect.AreEqual GenericSansSerif().FamilyName, f.Family.FamilyName, "the family of a font of an unknown family name" + Expect.AreEqual Ok, f.LastResult, "LastResult of a font of an unknown family name" + Expect.Done + End Sub + + [TestCase] + Public Sub Font_FromAnUnavailableFamily_IsNotAvailable() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bad As GdiPlus.Font = Font(FontFamily("No such family"), 12!) + Expect.IsFalse bad.IsAvailable, "IsAvailable of a font made from an unavailable family" + Expect.AreEqual InvalidParameter, bad.LastResult, "LastResult of a font made from an unavailable family" + Dim zero As GdiPlus.Font = Font(GenericSerif(), 0!) + Expect.IsFalse zero.IsAvailable, "IsAvailable of a font of size 0" + Expect.Done + End Sub + + [TestCase] + Public Sub Font_GetHeight_ScalesWithTheResolutionForPoints() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim px As GdiPlus.Font = Font(GenericSansSerif(), 20!, FontStyleRegular, UnitPixel) + Expect.IsTrue Abs(px.GetHeight(96!) - px.LineSpacing) < 0.01, "a pixel font has its LineSpacing at 96 dpi" + Expect.IsTrue Abs(px.GetHeight(300!) - px.LineSpacing) < 0.01, "a pixel font has its LineSpacing at 300 dpi" + Dim pt As GdiPlus.Font = Font(GenericSansSerif(), 10!, FontStyleRegular, UnitPoint) + Expect.IsTrue Abs(pt.GetHeight(72!) - pt.LineSpacing) < 0.01, "a point font has its LineSpacing at 72 dpi" + Expect.IsTrue Abs(pt.GetHeight(144!) - 2 * pt.LineSpacing) < 0.01, "a point font is twice as high at 144 dpi" + Expect.Done + End Sub + + [TestCase] + Public Sub Font_GetLogFontW_GivesANegativeHeightForAPixelFont() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim f As GdiPlus.Font = Font(GenericSansSerif(), 20!, FontStyleBold, UnitPixel) + Dim bmp As Bitmap = Bitmap(10!, 10!) + Dim g As Graphics = Graphics(bmp) + Dim lf As GDIP_LOGFONTW = f.GetLogFontW(g) + Expect.AreEqual -20, lf.lfHeight, "LOGFONTW height of a 20 pixel font" + Expect.AreEqual 700, lf.lfWeight, "LOGFONTW weight of a bold font" + Expect.Done + End Sub + + ' ---- the name Font, shared with the OLE Automation library + + [TestCase] + Public Sub Font_VariableDeclaredAsFont_HoldsAGdiPlusFont() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim f As Font + On Error Resume Next + Set f = Font(GenericSansSerif(), 12!) + Dim n As Long = Err.Number + Dim d As String = Err.Description + On Error GoTo 0 + Expect.AreEqual 0, n, "Err.Number after assigning a GDI+ font to a variable declared As Font (" & d & ")" + Expect.Done + End Sub + +End Module diff --git a/test/package-tests/GdiPlus/Sources/Fx_Helpers.twin b/test/package-tests/GdiPlus/Sources/Fx_Helpers.twin new file mode 100644 index 00000000..ca628423 --- /dev/null +++ b/test/package-tests/GdiPlus/Sources/Fx_Helpers.twin @@ -0,0 +1,38 @@ +' Fx_Helpers: what the effect, font and text cases of GdiPlus share. +Module Fx_Helpers + + ' A 4 by 4 bitmap of one opaque colour. + Public Function Fx_Flat(ByVal R As Long, ByVal G As Long, ByVal B As Long) As Bitmap + Dim bmp As Bitmap = Bitmap(4!, 4!) + Dim gr As Graphics = Graphics(bmp) + gr.Clear Color(255, R, G, B) + Return bmp + End Function + + ' A square bitmap of one opaque colour. + Public Function Fx_FlatSized(ByVal Size As Long, ByVal R As Long, ByVal G As Long, ByVal B As Long) As Bitmap + Dim bmp As Bitmap = Bitmap(CSng(Size), CSng(Size)) + Dim gr As Graphics = Graphics(bmp) + gr.Clear Color(255, R, G, B) + Return bmp + End Function + + ' The colour of pixel (1, 1) as AARRGGBB hex text. + Public Function Fx_Px(bmp As Bitmap) As String + Fx_Px = Right$("00000000" & Hex$(bmp.Pixel(1, 1).argb), 8) + End Function + + ' A colour matrix with 1 on the diagonal. + Public Function Fx_Identity() As ColorMatrix + Dim i As Long + For i = 0 To 4 + Fx_Identity.m(i, i) = 1 + Next + End Function + + ' The folder of the cases that write files. + Public Function Fx_Folder() As String + Fx_Folder = Environ$("TEMP") & "\tbdocs-package-tests\GdiPlus" + End Function + +End Module diff --git a/test/package-tests/GdiPlus/Sources/Fx_StringFormatTests.twin b/test/package-tests/GdiPlus/Sources/Fx_StringFormatTests.twin new file mode 100644 index 00000000..a8130060 --- /dev/null +++ b/test/package-tests/GdiPlus/Sources/Fx_StringFormatTests.twin @@ -0,0 +1,288 @@ +' Fx_StringFormatTests: StringFormat. +[TestFixture] +Module Fx_StringFormatTests + + ' ---- the generic formats are GDI+'s shared statics + + [TestCase] + Public Sub GenericDefaultStringFormat_ChangeIsSeenByEveryLaterCall() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim d As StringFormat = GenericDefaultStringFormat() + Expect.IsFalse d.NoWrap, "NoWrap of the generic default format at the start" + d.NoWrap = True + Expect.IsTrue GenericDefaultStringFormat().NoWrap, "NoWrap of a later GenericDefaultStringFormat() after changing the first" + d.NoWrap = False + Expect.IsFalse GenericDefaultStringFormat().NoWrap, "NoWrap of the generic default format after it is set back" + Expect.Done + End Sub + + [TestCase] + Public Sub GenericDefaultStringFormat_CloneIsNotShared() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim mine As StringFormat = GenericDefaultStringFormat().Clone() + mine.NoWrap = True + Expect.IsFalse GenericDefaultStringFormat().NoWrap, "NoWrap of the generic default format after changing a clone" + Expect.Done + End Sub + + [TestCase] + Public Sub GenericTypographicStringFormat_ChangeIsSeenByEveryLaterCall() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim t As StringFormat = GenericTypographicStringFormat() + Expect.IsTrue t.LineLimit, "LineLimit of the generic typographic format" + t.LineLimit = False + Expect.IsFalse GenericTypographicStringFormat().LineLimit, "LineLimit of a later GenericTypographicStringFormat() after changing the first" + t.LineLimit = True + Expect.IsTrue GenericTypographicStringFormat().LineLimit, "LineLimit of the generic typographic format after it is set back" + Expect.Done + End Sub + + [TestCase] + Public Sub GenericFormats_HaveTheDocumentedSettings() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim d As StringFormat = GenericDefaultStringFormat() + Expect.AreEqual 0, d.Flags, "Flags of the generic default format" + Expect.IsTrue d.IsTrimmingCharacter, "IsTrimmingCharacter of the generic default format" + Dim t As StringFormat = GenericTypographicStringFormat() + Expect.IsTrue t.NoFitBlackBox, "NoFitBlackBox of the generic typographic format" + Expect.IsTrue t.LineLimit, "LineLimit of the generic typographic format" + Expect.IsTrue t.NoClip, "NoClip of the generic typographic format" + Expect.IsTrue t.IsTrimmingNone, "IsTrimmingNone of the generic typographic format" + Expect.Done + End Sub + + ' ---- Alignment + + [TestCase] + Public Sub Alignment_GetAfterSettingCenter_ReturnsCenter() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim sf As StringFormat = StringFormat() + sf.Alignment = StringAlignmentCenter + Expect.AreEqual StringAlignmentCenter, sf.Alignment, "Alignment after setting StringAlignmentCenter" + Expect.Done + End Sub + + [TestCase] + Public Sub Alignment_GetAfterSettingFar_ReturnsFar() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim sf As StringFormat = StringFormat() + sf.SetAlignmentFar + Expect.AreEqual StringAlignmentFar, sf.Alignment, "Alignment after SetAlignmentFar" + Expect.Done + End Sub + + [TestCase] + Public Sub Alignment_Get_LeavesLastResultOk() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim sf As StringFormat = StringFormat() + sf.Alignment = StringAlignmentCenter + Dim a As GpStringAlignment = sf.Alignment + Expect.AreEqual Ok, sf.LastResult, "LastResult after reading Alignment" + Expect.Done + End Sub + + [TestCase] + Public Sub IsAlignmentCenter_AfterSettingCenter_IsTrue() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim sf As StringFormat = StringFormat() + sf.SetAlignmentCenter + Expect.IsTrue sf.IsAlignmentCenter, "IsAlignmentCenter after SetAlignmentCenter" + Expect.IsFalse sf.IsAlignmentNear, "IsAlignmentNear after SetAlignmentCenter" + Expect.IsFalse sf.IsAlignmentFar, "IsAlignmentFar after SetAlignmentCenter" + Expect.Done + End Sub + + [TestCase] + Public Sub IsAlignmentFar_AfterSettingFar_IsTrue() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim sf As StringFormat = StringFormat() + sf.Alignment = StringAlignmentFar + Expect.IsTrue sf.IsAlignmentFar, "IsAlignmentFar after setting StringAlignmentFar" + Expect.IsFalse sf.IsAlignmentNear, "IsAlignmentNear after setting StringAlignmentFar" + Expect.Done + End Sub + + [TestCase] + Public Sub Alignment_SetFar_PlacesTheTextAtTheRightEdge() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = Bitmap(100!, 30!) + Dim g As Graphics = Graphics(bmp) + Dim f As GdiPlus.Font = Font(GenericSansSerif(), 12!) + Dim black As SolidBrush = SolidBrush(Color(255, 0, 0, 0)) + Dim sf As StringFormat = StringFormat() + sf.SetAlignmentFar + g.Clear Color(255, 255, 255, 255) + g.DrawString "I", f, GpRectF(0, 0, 100, 30), sf, black + Dim x As Long, y As Long, inkLeft As Boolean, inkRight As Boolean + For y = 0 To 29 + For x = 0 To 99 + If bmp.Pixel(x, y).R < 128 Then + If x < 50 Then inkLeft = True Else inkRight = True + End If + Next + Next + Expect.IsFalse inkLeft, "dark pixels in the left half with far alignment" + Expect.IsTrue inkRight, "dark pixels in the right half with far alignment" + Expect.Done + End Sub + + ' ---- tab stops + + Private Function Fx_FormatWithTabs() As StringFormat + Dim sf As StringFormat = StringFormat() + Dim tabs(0 To 1) As Single + tabs(0) = 4: tabs(1) = 8 + sf.SetTabStops 5, tabs + Return sf + End Function + + [TestCase] + Public Sub GetTabStops_FirstForm_ReturnsTheFirstTabOffset() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim sf As StringFormat = Fx_FormatWithTabs() + Dim first As Single + Dim got() As Single = sf.GetTabStops(first) + Expect.AreEqual 5, first, "the first tab offset returned by GetTabStops(firstTabOffset)" + Expect.Done + End Sub + + [TestCase] + Public Sub GetTabStops_FirstForm_ReturnsTheTabStops() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim sf As StringFormat = Fx_FormatWithTabs() + Dim first As Single + Dim got() As Single = sf.GetTabStops(first) + Expect.AreEqual 1, LBound(got), "lower bound of the array of tab stops" + Expect.AreEqual 2, UBound(got), "upper bound of the array of tab stops" + Expect.AreEqual 4, got(1), "first tab stop" + Expect.AreEqual 8, got(2), "second tab stop" + Expect.Done + End Sub + + [TestCase] + Public Sub GetTabStops_SecondForm_ReturnsTheFirstTabOffsetAndTheStops() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim sf As StringFormat = Fx_FormatWithTabs() + Expect.AreEqual 2, sf.TabStopCount, "TabStopCount" + Dim first As Single + Dim back(0 To 1) As Single + Expect.AreEqual Ok, sf.GetTabStops(2, first, back(0)), "GetTabStops(2, first, back(0))" + Expect.AreEqual 5, first, "the first tab offset of the second form" + Expect.AreEqual 4, back(0), "first tab stop of the second form" + Expect.AreEqual 8, back(1), "second tab stop of the second form" + Expect.Done + End Sub + + ' ---- digit substitution + + [TestCase] + Public Sub SetDigitSubstitutionUser_KeepsTheLanguage() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim sf As StringFormat = StringFormat() + sf.SetDigitSubstitution 1031, StringDigitSubstituteNational + sf.SetDigitSubstitutionUser + Expect.AreEqual 1031, sf.DigitSubstitutionLanguage, "language after SetDigitSubstitutionUser" + Expect.AreEqual StringDigitSubstituteUser, sf.DigitSubstitutionMethod, "method after SetDigitSubstitutionUser" + Expect.Done + End Sub + + [TestCase] + Public Sub SetDigitSubstitutionNone_KeepsTheLanguage() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim sf As StringFormat = StringFormat() + sf.SetDigitSubstitution 1031, StringDigitSubstituteNational + sf.SetDigitSubstitutionNone + Expect.AreEqual 1031, sf.DigitSubstitutionLanguage, "language after SetDigitSubstitutionNone" + Expect.AreEqual StringDigitSubstituteNone, sf.DigitSubstitutionMethod, "method after SetDigitSubstitutionNone" + Expect.Done + End Sub + + [TestCase] + Public Sub SetDigitSubstitutionNational_KeepsTheLanguage() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim sf As StringFormat = StringFormat() + sf.SetDigitSubstitution 1031, StringDigitSubstituteNone + sf.SetDigitSubstitutionNational + Expect.AreEqual 1031, sf.DigitSubstitutionLanguage, "language after SetDigitSubstitutionNational" + Expect.AreEqual StringDigitSubstituteNational, sf.DigitSubstitutionMethod, "method after SetDigitSubstitutionNational" + Expect.Done + End Sub + + [TestCase] + Public Sub SetDigitSubstitutionTraditional_KeepsTheLanguage() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim sf As StringFormat = StringFormat() + sf.SetDigitSubstitution 1031, StringDigitSubstituteNational + sf.SetDigitSubstitutionTraditional + Expect.AreEqual 1031, sf.DigitSubstitutionLanguage, "language after SetDigitSubstitutionTraditional" + Expect.AreEqual StringDigitSubstituteTraditional, sf.DigitSubstitutionMethod, "method after SetDigitSubstitutionTraditional" + Expect.Done + End Sub + + [TestCase] + Public Sub DigitSubstitutionLanguage_Let_KeepsTheMethod() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim sf As StringFormat = StringFormat() + sf.SetDigitSubstitution 1031, StringDigitSubstituteNational + sf.DigitSubstitutionLanguage = 1033 + Expect.AreEqual 1033, sf.DigitSubstitutionLanguage, "language after setting DigitSubstitutionLanguage" + Expect.AreEqual StringDigitSubstituteNational, sf.DigitSubstitutionMethod, "method after setting DigitSubstitutionLanguage" + Expect.Done + End Sub + + [TestCase] + Public Sub DigitSubstitutionMethod_Let_KeepsTheLanguage() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim sf As StringFormat = StringFormat() + sf.SetDigitSubstitution 1031, StringDigitSubstituteNational + sf.DigitSubstitutionMethod = StringDigitSubstituteTraditional + Expect.AreEqual 1031, sf.DigitSubstitutionLanguage, "language after setting DigitSubstitutionMethod" + Expect.AreEqual StringDigitSubstituteTraditional, sf.DigitSubstitutionMethod, "method after setting DigitSubstitutionMethod" + Expect.Done + End Sub + + [TestCase] + Public Sub SetDigitSubstitution_SetsLanguageAndMethodTogether() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim sf As StringFormat = StringFormat() + Expect.AreEqual Ok, sf.SetDigitSubstitution(1031, StringDigitSubstituteNational), "SetDigitSubstitution(1031, National)" + Expect.AreEqual 1031, sf.DigitSubstitutionLanguage, "language after SetDigitSubstitution" + Expect.AreEqual StringDigitSubstituteNational, sf.DigitSubstitutionMethod, "method after SetDigitSubstitution" + Expect.Done + End Sub + + ' ---- measurable character ranges + + [TestCase] + Public Sub MeasurableCharacterRanges_MoreThan32_ReturnsValueOverflow() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim sf As StringFormat = StringFormat() + Dim many(0 To 32) As CharacterRange + Expect.AreEqual Ok, sf.SetMeasurableCharacterRanges(32, many(0)), "SetMeasurableCharacterRanges with 32 ranges" + Expect.AreEqual ValueOverflow, sf.SetMeasurableCharacterRanges(33, many(0)), "SetMeasurableCharacterRanges with 33 ranges" + Expect.AreEqual 32, sf.MeasurableCharacterRangeCount, "MeasurableCharacterRangeCount after the refused call" + Expect.Done + End Sub + +End Module diff --git a/test/package-tests/GdiPlus/Sources/Gr_BaseTests.twin b/test/package-tests/GdiPlus/Sources/Gr_BaseTests.twin new file mode 100644 index 00000000..f3b2f59a --- /dev/null +++ b/test/package-tests/GdiPlus/Sources/Gr_BaseTests.twin @@ -0,0 +1,126 @@ +' GdiPlusBase: the last result and the error handling that every class of the package inherits. +[TestFixture] +Module Gr_BaseTests + + Private mFailure As String + + ' The handler of the HandleErrors case: records the type of the object and its status. + Public Function Gr_OnError(ByVal Obj As GdiPlusBase) As Variant + mFailure = TypeName(Obj) & " " & Obj.LastResult + End Function + + ' A bitmap and a read outside it, which fails with InvalidParameter. + Private Function ReadOutside(ByVal Bmp As Bitmap) As Color + Return Bmp.Pixel(100, 100) + End Function + + ' ---- LastResult + + [TestCase] + Public Sub LastResult_AfterAFailedCall_IsTheStatus() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = Bitmap(8!, 8!) + Expect.AreEqual 0, CLng(bmp.LastResult), "LastResult of a new Bitmap" + Expect.AreEqual "Ok", bmp.Status, "Status of a new Bitmap" + Dim c As Color = ReadOutside(bmp) + Expect.AreEqual CLng(InvalidParameter), CLng(bmp.LastResult), "LastResult after reading a pixel outside the bitmap" + Expect.AreEqual "Invalid Parameter", bmp.Status, "Status after reading a pixel outside the bitmap" + Expect.AreEqual "Invalid Parameter" & vbCrLf, bmp.StatusNL, "StatusNL after reading a pixel outside the bitmap" + Expect.Done + End Sub + + [TestCase] + Public Sub LastResult_ASuccessfulCall_DoesNotClearIt() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = Bitmap(8!, 8!) + Dim c As Color = ReadOutside(bmp) + c = bmp.Pixel(1, 1) + Expect.AreEqual CLng(InvalidParameter), CLng(bmp.LastResult), "LastResult after a good call that follows a failed one" + Expect.Done + End Sub + + [TestCase] + Public Sub GetLastResult_ReturnsTheStatusAndClearsIt() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = Bitmap(8!, 8!) + Dim c As Color = ReadOutside(bmp) + Expect.AreEqual CLng(InvalidParameter), CLng(bmp.GetLastResult()), "GetLastResult after a failed call" + Expect.AreEqual 0, CLng(bmp.LastResult), "LastResult after GetLastResult" + c = ReadOutside(bmp) + Expect.AreEqual CLng(InvalidParameter), CLng(bmp.ClearResult()), "ClearResult after a failed call" + Expect.AreEqual 0, CLng(bmp.LastResult), "LastResult after ClearResult" + Expect.Done + End Sub + + ' ---- the error handling + + [TestCase] + Public Sub GdipErrorHandlingStrategy_Default_IgnoresErrors() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Expect.AreEqual CLng(IgnoreErrors), CLng(GdipErrorHandlingStrategy), "the default GdipErrorHandlingStrategy" + Expect.AreEqual 3300, CLng(GdipErrorCodeBase), "the default GdipErrorCodeBase" + Dim bmp As Bitmap = Bitmap(8!, 8!) + On Error Resume Next + Dim c As Color = ReadOutside(bmp) + Dim raised As Long = Err.Number + On Error GoTo 0 + Expect.AreEqual 0, raised, "the error number of a failed call under IgnoreErrors" + Expect.Done + End Sub + + [TestCase] + Public Sub RaiseErrors_AFailedCall_RaisesTheStatusPlusTheBase() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = Bitmap(8!, 8!) + GdipErrorHandlingStrategy = RaiseErrors + On Error Resume Next + Dim c As Color = ReadOutside(bmp) + Dim number As Long = Err.Number + Dim source As String = Err.Source + Dim description As String = Err.Description + On Error GoTo 0 + GdipErrorHandlingStrategy = IgnoreErrors + Expect.AreEqual 3302, number, "the error number of a failed call under RaiseErrors" + Expect.AreEqual "GdiPlus:Bitmap", source, "the error source of a failed call under RaiseErrors" + Expect.AreEqual "2", description, "the error description of a failed call under RaiseErrors" + Expect.Done + End Sub + + [TestCase] + Public Sub RaiseErrors_ACodeBase_IsAddedToTheStatus() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = Bitmap(8!, 8!) + Dim oldBase As Long = GdipErrorCodeBase + GdipErrorCodeBase = 5000 + GdipErrorHandlingStrategy = RaiseErrors + On Error Resume Next + Dim c As Color = ReadOutside(bmp) + Dim number As Long = Err.Number + On Error GoTo 0 + GdipErrorHandlingStrategy = IgnoreErrors + GdipErrorCodeBase = oldBase + Expect.AreEqual 5002, number, "the error number of a failed call under RaiseErrors with GdipErrorCodeBase 5000" + Expect.Done + End Sub + + [TestCase] + Public Sub HandleErrors_AFailedCall_CallsTheHandler() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = Bitmap(8!, 8!) + mFailure = "" + GdipErrorHandlingStrategy = HandleErrors + GdipErrorHandler = CType(Of GdipErrorHandler)(AddressOf Gr_OnError) + Dim c As Color = ReadOutside(bmp) + GdipErrorHandlingStrategy = IgnoreErrors + Expect.AreEqual "Bitmap 2", mFailure, "the type and status the handler received for a failed call under HandleErrors" + Expect.Done + End Sub + +End Module diff --git a/test/package-tests/GdiPlus/Sources/Gr_GraphicsTests.twin b/test/package-tests/GdiPlus/Sources/Gr_GraphicsTests.twin new file mode 100644 index 00000000..f9f445b0 --- /dev/null +++ b/test/package-tests/GdiPlus/Sources/Gr_GraphicsTests.twin @@ -0,0 +1,462 @@ +' Graphics: creating it, clearing, the world transformation, the clipping area and the calls that do not return. +[TestFixture] +Module Gr_GraphicsTests + + ' A source image of 4 x 2 pixels, the left half red and the right half blue. + Private Function Source() As Bitmap + Dim src As Bitmap = Bitmap(4, 2, PixelFormat32bppARGB) + Dim gs As Graphics = Graphics(src) + gs.Clear Color(255, 0, 0) + gs.FillRectangle SolidBrush(Color(0, 0, 255)), 2, 0, 2, 2 + Return src + End Function + + ' A transparent destination of 20 x 10 pixels, with the settings that make the scaled pixels exact. + Private Function Target(ByRef Gfx As Graphics) As Bitmap + Dim dst As Bitmap = Bitmap(20, 10, PixelFormat32bppARGB) + Set Gfx = Graphics(dst) + Gfx.InterpolationMode = InterpolationModeNearestNeighbor + Gfx.PixelOffsetMode = PixelOffsetModeHalf + Return dst + End Function + + ' ---- creating + + [TestCase] + Public Sub Graphics_NothingImage_ReportsError() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim g As Graphics = Graphics(Nothing) + Expect.IsTrue g.LastResult <> Ok, "LastResult of Graphics(Nothing) is an error status" + Expect.Done + End Sub + + [TestCase] + Public Sub NewGraphics_NothingImage_ReportsError() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim g As Graphics = New Graphics(Nothing) + Expect.IsTrue g.LastResult <> Ok, "LastResult of New Graphics(Nothing) is an error status" + Expect.Done + End Sub + + [TestCase] + Public Sub Graphics_NothingImage_DrawingFailsWithInvalidParameter() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim g As Graphics = Graphics(Nothing) + Expect.AreEqual CLng(InvalidParameter), CLng(g.Clear(Color(0, 0, 255))), "Clear on a Graphics made from Nothing returns InvalidParameter" + Expect.Done + End Sub + + ' ---- Clear + + [TestCase] + Public Sub Clear_VBColourRed_FillsRed() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = Gr_White(4, 4) + Dim g As Graphics = Graphics(bmp) + g.Clear vbRed + Expect.AreEqual "FFFF0000", Gr_Hex(Gr_Px(bmp, 1, 1)), "pixel after Clear(vbRed)" + Expect.Done + End Sub + + [TestCase] + Public Sub Clear_RGBFunction_FillsThatColour() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = Gr_White(4, 4) + Dim g As Graphics = Graphics(bmp) + g.Clear RGB(255, 0, 0) + Expect.AreEqual "FFFF0000", Gr_Hex(Gr_Px(bmp, 1, 1)), "pixel after Clear(RGB(255, 0, 0))" + g.Clear RGB(10, 20, 30) + Expect.AreEqual "FF0A141E", Gr_Hex(Gr_Px(bmp, 1, 1)), "pixel after Clear(RGB(10, 20, 30))" + Expect.Done + End Sub + + [TestCase] + Public Sub Clear_CColorTransparent_KeepsAlpha() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = Gr_White(4, 4) + Dim g As Graphics = Graphics(bmp) + g.Clear CColor.Transparent + Dim c As Color = bmp.Pixel(1, 1) + Expect.AreEqual 0, CLng(c.A), "alpha of a white bitmap after Clear(CColor.Transparent)" + Expect.Done + End Sub + + [TestCase] + Public Sub Clear_ColorForm_FillsExactColour() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = Gr_White(4, 4) + Dim g As Graphics = Graphics(bmp) + g.Clear Color(255, 0, 0) + Expect.AreEqual "FFFF0000", Gr_Hex(Gr_Px(bmp, 1, 1)), "pixel after Clear(Color(255, 0, 0))" + g.Clear Color(0, 0, 0, 0) + Expect.AreEqual 0, CLng(bmp.Pixel(1, 1).A), "alpha after Clear(Color(0, 0, 0, 0))" + Expect.Done + End Sub + + ' ---- DrawLine + + [TestCase] + Public Sub DrawLine_WholeNumbers_DrawsBetweenThePoints() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = Gr_White(16, 12) + Dim g As Graphics = Graphics(bmp) + g.SmoothingMode = SmoothingModeNone + Dim p As Pen = Pen(Color(255, 0, 0), 1) + Expect.AreEqual 0, CLng(g.DrawLine(p, 0, 5, 15, 5)), "status of DrawLine(pen, 0, 5, 15, 5)" + Expect.AreEqual "FFFF0000", Gr_Hex(Gr_Px(bmp, 0, 5)), "pixel at the start of the line" + Expect.AreEqual "FFFF0000", Gr_Hex(Gr_Px(bmp, 15, 5)), "pixel at the end of the line" + Expect.AreEqual "FFFF0000", Gr_Hex(Gr_Px(bmp, 8, 5)), "pixel in the middle of the line" + Expect.AreEqual "FFFFFFFF", Gr_Hex(Gr_Px(bmp, 8, 6)), "pixel below the line" + Dim bmp2 As Bitmap = Gr_White(16, 12) + Dim g2 As Graphics = Graphics(bmp2) + g2.SmoothingMode = SmoothingModeNone + g2.DrawLineI p, 0, 5, 15, 5 + Expect.AreEqual "FFFF0000", Gr_Hex(Gr_Px(bmp2, 8, 5)), "pixel in the middle of the line of DrawLineI" + Expect.Done + End Sub + + ' ---- text + + [TestCase] + Public Sub DrawString_PrefixedFont_Draws() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = Gr_Clear(120, 40) + Dim g As Graphics = Graphics(bmp) + Dim f As GdiPlus.Font = GdiPlus.Font("Arial", 14) + Expect.AreEqual 0, CLng(g.DrawString("Text", f, 10, 5, SolidBrush(Color(0, 0, 0)))), "status of DrawString with GdiPlus.Font(""Arial"", 14)" + Expect.IsTrue Gr_CountNot(bmp, 0) > 20, "pixels drawn by DrawString with GdiPlus.Font(""Arial"", 14)" + Expect.Done + End Sub + + [TestCase] + Public Sub Font_AssignedToAVariableOfTheStdoleType_Fails() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim f As Font + On Error Resume Next + Set f = Font("Arial", 14) + Dim failed As Boolean = (Err.Number <> 0) Or (f Is Nothing) + On Error GoTo 0 + Expect.IsTrue failed, "assigning Font(""Arial"", 14) to a variable declared As Font (the stdole type) raises an error or leaves Nothing" + Expect.Done + End Sub + + [TestCase] + Public Sub Font_AssignedToAGdiPlusFontVariable_IsUsable() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim f As GdiPlus.Font = Font("Arial", 14) + Expect.AreEqual 0, CLng(f.LastResult), "LastResult of Font(""Arial"", 14) assigned to a GdiPlus.Font variable" + Dim bmp As Bitmap = Gr_Clear(120, 40) + Dim g As Graphics = Graphics(bmp) + Expect.AreEqual 0, CLng(g.DrawString("Text", f, 10, 5, SolidBrush(Color(0, 0, 0)))), "status of DrawString with that font" + Expect.IsTrue Gr_CountNot(bmp, 0) > 20, "pixels drawn with that font" + Expect.Done + End Sub + + [TestCase] + Public Sub DrawString_ClearTypeGridFitWithSourceCopy_ReturnsInvalidParameter() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim f As GdiPlus.Font = GdiPlus.Font("Arial", 14) + Dim black As SolidBrush = SolidBrush(Color(0, 0, 0)) + + Dim bmp As Bitmap = Gr_Clear(120, 40) + Dim g As Graphics = Graphics(bmp) + g.TextRenderingHint = TextRenderingHintClearTypeGridFit + g.CompositingMode = CompositingModeSourceOver + Expect.AreEqual 0, CLng(g.DrawString("Text", f, 10, 5, black)), "status of DrawString with ClearTypeGridFit and SourceOver" + Expect.IsTrue Gr_CountNot(bmp, 0) > 20, "pixels drawn with ClearTypeGridFit and SourceOver" + + Dim bmp2 As Bitmap = Gr_Clear(120, 40) + Dim g2 As Graphics = Graphics(bmp2) + g2.TextRenderingHint = TextRenderingHintClearTypeGridFit + g2.CompositingMode = CompositingModeSourceCopy + Expect.AreEqual CLng(InvalidParameter), CLng(g2.DrawString("Text", f, 10, 5, black)), "status of DrawString with ClearTypeGridFit and SourceCopy" + Expect.AreEqual 0, Gr_CountNot(bmp2, 0), "pixels drawn with ClearTypeGridFit and SourceCopy" + Expect.Done + End Sub + + [TestCase] + Public Sub DrawDriverString_BrushVariableAndOptions_Draws() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = Gr_Clear(60, 30) + Dim g As Graphics = Graphics(bmp) + Dim f As GdiPlus.Font = GdiPlus.Font("Arial", 14) + Dim brush As Brush = SolidBrush(Color(0, 0, 0)) + Dim options As GpDriverStringOptions + options = DriverStringOptionsCmapLookup + Dim noMatrix As Matrix = Nothing + Expect.AreEqual 0, CLng(g.DrawDriverString("Hi", f, brush, GpPointF(5, 5), options, noMatrix)), "status of DrawDriverString with a Brush variable and a GpDriverStringOptions variable" + Expect.IsTrue Gr_CountNot(bmp, 0) > 5, "pixels drawn by DrawDriverString" + Expect.Done + End Sub + + ' ---- DrawImage forms that do not return + + [TestCase] + Public Sub DrawImage_DestRectAndSourceRect_DrawsThePart() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim src As Bitmap = Source() + Dim g As Graphics + Dim dst As Bitmap = Target(g) + Expect.AreEqual 0, CLng(g.DrawImage(src, GpRectF(10, 2, 6, 4), GpRectF(2, 0, 2, 2), UnitPixel)), "status of DrawImage(image, destRect, srcRect, UnitPixel)" + Expect.AreEqual "FF0000FF", Gr_Hex(Gr_Px(dst, 10, 2)), "pixel at the top left of the destination: the blue half of the image" + Expect.AreEqual "FF0000FF", Gr_Hex(Gr_Px(dst, 15, 5)), "pixel at the bottom right of the destination" + Expect.AreEqual 0, Gr_Px(dst, 16, 5), "pixel right of the destination" + Expect.Done + End Sub + + [TestCase] + Public Sub DrawImage_DestRectF_ScalesTheImage() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim src As Bitmap = Source() + Dim g As Graphics + Dim dst As Bitmap = Target(g) + Expect.AreEqual 0, CLng(g.DrawImage(src, GpRectF(1, 1, 8, 4))), "status of DrawImage(image, GpRectF) 1, 1, 8, 4" + Expect.AreEqual "FFFF0000", Gr_Hex(Gr_Px(dst, 3, 3)), "left half of the scaled image" + Expect.AreEqual "FF0000FF", Gr_Hex(Gr_Px(dst, 7, 3)), "right half of the scaled image" + Expect.AreEqual 0, Gr_Px(dst, 9, 3), "pixel right of the scaled image" + Expect.Done + End Sub + + [TestCase] + Public Sub DrawImage_DestRect_ScalesTheImage() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim src As Bitmap = Source() + Dim g As Graphics + Dim dst As Bitmap = Target(g) + Expect.AreEqual 0, CLng(g.DrawImage(src, GpRect(1, 1, 8, 4))), "status of DrawImage(image, GpRect) 1, 1, 8, 4" + Expect.AreEqual "FFFF0000", Gr_Hex(Gr_Px(dst, 3, 3)), "left half of the scaled image" + Expect.AreEqual "FF0000FF", Gr_Hex(Gr_Px(dst, 7, 3)), "right half of the scaled image" + Expect.AreEqual 0, Gr_Px(dst, 9, 3), "pixel right of the scaled image" + Expect.Done + End Sub + + [TestCase] + Public Sub DrawImage_ThreeFloatPointsAndSourcePart_DrawsTheImage() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim src As Bitmap = Source() + Dim g As Graphics + Dim dst As Bitmap = Target(g) + Dim pts(0 To 2) As GpPointF + pts(0) = GpPointF(1, 1) + pts(1) = GpPointF(9, 1) + pts(2) = GpPointF(1, 5) + Expect.AreEqual 0, CLng(g.DrawImage(src, pts, 0, 0, 4, 2, UnitPixel)), "status of DrawImage(image, destPoints (GpPointF), 0, 0, 4, 2, UnitPixel)" + Expect.AreEqual "FFFF0000", Gr_Hex(Gr_Px(dst, 3, 3)), "left half of the image" + Expect.AreEqual "FF0000FF", Gr_Hex(Gr_Px(dst, 7, 3)), "right half of the image" + Expect.Done + End Sub + + [TestCase] + Public Sub DrawImage_ThreeIntegerPointsAndSourcePart_DrawsTheImage() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim src As Bitmap = Source() + Dim g As Graphics + Dim dst As Bitmap = Target(g) + Dim pts(0 To 2) As GpPoint + pts(0) = GpPoint(1, 1) + pts(1) = GpPoint(9, 1) + pts(2) = GpPoint(1, 5) + Expect.AreEqual 0, CLng(g.DrawImage(src, pts, 0, 0, 4, 2, UnitPixel)), "status of DrawImage(image, destPoints (GpPoint), 0, 0, 4, 2, UnitPixel)" + Expect.AreEqual "FFFF0000", Gr_Hex(Gr_Px(dst, 3, 3)), "left half of the image" + Expect.AreEqual "FF0000FF", Gr_Hex(Gr_Px(dst, 7, 3)), "right half of the image" + Expect.Done + End Sub + + [TestCase] + Public Sub DrawImage_XYWidthHeight_ScalesTheImage() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim src As Bitmap = Source() + Dim g As Graphics + Dim dst As Bitmap = Target(g) + Expect.AreEqual 0, CLng(g.DrawImage(src, 1, 1, 8, 4)), "status of DrawImage(image, 1, 1, 8, 4)" + Expect.AreEqual "FFFF0000", Gr_Hex(Gr_Px(dst, 3, 3)), "left half of the scaled image" + Expect.AreEqual "FF0000FF", Gr_Hex(Gr_Px(dst, 7, 3)), "right half of the scaled image" + Expect.Done + End Sub + + ' ---- clipping + + [TestCase] + Public Sub Clip_Get_ReturnsTheClippingRegion() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = Gr_White(40, 40) + Dim g As Graphics = Graphics(bmp) + g.SetClip GpRect(5, 5, 12, 8) + g.ClearResult + Dim r As Region = g.Clip + Expect.IsTrue Not (r Is Nothing), "Clip returns a Region after SetClip" + Expect.AreEqual 0, CLng(g.LastResult), "LastResult after reading Clip" + Expect.Done + End Sub + + [TestCase] + Public Sub ClipBounds_AfterSetClip_ReturnsTheRectangle() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = Gr_White(40, 40) + Dim g As Graphics = Graphics(bmp) + g.SetClip GpRect(5, 5, 12, 8) + Dim b As GpRectF = g.ClipBounds + Expect.IsTrue b.X = 5 And b.Y = 5 And b.Width = 12 And b.Height = 8, "ClipBounds after SetClip(GpRect(5, 5, 12, 8))" + Expect.Done + End Sub + + [TestCase] + Public Sub IsVisible_GpPointF_ReturnsWhetherThePointIsInside() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = Gr_White(40, 40) + Dim g As Graphics = Graphics(bmp) + g.SetClip GpRect(5, 5, 12, 8) + Expect.IsTrue g.IsVisible(GpPointF(6, 6)), "IsVisible(GpPointF(6, 6)) inside the clipping rectangle" + Expect.IsFalse g.IsVisible(GpPointF(2, 2)), "IsVisible(GpPointF(2, 2)) outside the clipping rectangle" + Expect.Done + End Sub + + [TestCase] + Public Sub IsVisible_Alternatives_Work() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = Gr_White(40, 40) + Dim g As Graphics = Graphics(bmp) + g.SetClip GpRect(5, 5, 12, 8) + Expect.IsTrue g.IsVisible(6, 6), "IsVisible(6, 6) inside the clipping rectangle" + Expect.IsFalse g.IsVisible(2, 2), "IsVisible(2, 2) outside the clipping rectangle" + Expect.IsTrue g.IsVisible(GpPoint(6, 6)), "IsVisible(GpPoint(6, 6)) inside the clipping rectangle" + Expect.IsFalse g.IsVisible(GpPoint(2, 2)), "IsVisible(GpPoint(2, 2)) outside the clipping rectangle" + Expect.Done + End Sub + + ' ---- the world transformation + + [TestCase] + Public Sub Transform_Get_ReturnsTheWorldTransformation() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = Gr_White(10, 10) + Dim g As Graphics = Graphics(bmp) + g.TranslateTransform 3, 4 + g.ClearResult + Dim m As Matrix = g.Transform + Expect.IsTrue Not (m Is Nothing), "Graphics.Transform returns a Matrix" + If Not (m Is Nothing) Then + Expect.AreEqual 3, CLng(m.OffsetX), "OffsetX of the matrix that Transform returns after TranslateTransform 3, 4" + Expect.AreEqual 4, CLng(m.OffsetY), "OffsetY of the matrix that Transform returns after TranslateTransform 3, 4" + End If + Expect.AreEqual 0, CLng(g.LastResult), "LastResult after reading Transform" + Expect.Done + End Sub + + [TestCase] + Public Sub GetTransform_CopiesTheWorldTransformation() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = Gr_White(10, 10) + Dim g As Graphics = Graphics(bmp) + g.TranslateTransform 3, 4 + Dim m As Matrix = Matrix() + Expect.AreEqual 0, CLng(g.GetTransform(m)), "status of GetTransform" + Expect.AreEqual 3, CLng(m.OffsetX), "OffsetX of the matrix GetTransform fills" + Expect.AreEqual 4, CLng(m.OffsetY), "OffsetY of the matrix GetTransform fills" + Expect.Done + End Sub + + [TestCase] + Public Sub ShearTransform_ShearsTheCoordinates() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = Gr_White(8, 8) + Dim g As Graphics = Graphics(bmp) + Expect.AreEqual 0, CLng(g.ShearTransform(0.5, 0)), "status of ShearTransform(0.5, 0)" + Dim pts(1 To 1) As GpPointF + pts(1) = GpPointF(1, 2) + g.TransformPoints CoordinateSpaceDevice, CoordinateSpaceWorld, pts + Expect.AreEqual 2, CLng(pts(1).X), "X of the point (1, 2) after ShearTransform(0.5, 0): x moves by half of y" + Expect.AreEqual 2, CLng(pts(1).Y), "Y of the point (1, 2) after ShearTransform(0.5, 0)" + Expect.Done + End Sub + + [TestCase] + Public Sub MultiplyTransform_ShearMatrix_ShearsTheCoordinates() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = Gr_White(8, 8) + Dim g As Graphics = Graphics(bmp) + Expect.AreEqual 0, CLng(g.MultiplyTransform(Matrix(1, 0, 0.5, 1, 0, 0))), "status of MultiplyTransform with a shear matrix" + Dim pts(1 To 1) As GpPointF + pts(1) = GpPointF(1, 2) + g.TransformPoints CoordinateSpaceDevice, CoordinateSpaceWorld, pts + Expect.AreEqual 2, CLng(pts(1).X), "X of the point (1, 2) after multiplying by Matrix(1, 0, 0.5, 1, 0, 0)" + Expect.AreEqual 2, CLng(pts(1).Y), "Y of the point (1, 2) after multiplying by Matrix(1, 0, 0.5, 1, 0, 0)" + Expect.Done + End Sub + + ' ---- modes that GDI+ replaces + + [TestCase] + Public Sub InterpolationMode_HighQuality_ReadsBackAsBicubic() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim g As Graphics = Graphics(Gr_White(4, 4)) + g.InterpolationMode = InterpolationModeHighQuality + Expect.AreEqual CLng(InterpolationModeHighQualityBicubic), CLng(g.InterpolationMode), "InterpolationMode after setting InterpolationModeHighQuality" + Expect.Done + End Sub + + [TestCase] + Public Sub InterpolationMode_LowQualityAndDefault_ReadBackAsBilinear() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim g As Graphics = Graphics(Gr_White(4, 4)) + g.InterpolationMode = InterpolationModeLowQuality + Expect.AreEqual CLng(InterpolationModeBilinear), CLng(g.InterpolationMode), "InterpolationMode after setting InterpolationModeLowQuality" + g.InterpolationMode = InterpolationModeNearestNeighbor + g.InterpolationMode = InterpolationModeDefault + Expect.AreEqual CLng(InterpolationModeBilinear), CLng(g.InterpolationMode), "InterpolationMode after setting InterpolationModeDefault" + Expect.Done + End Sub + + [TestCase] + Public Sub SmoothingMode_HighQuality_ReadsBackAsAntiAlias() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim g As Graphics = Graphics(Gr_White(4, 4)) + g.SmoothingMode = SmoothingModeHighQuality + Expect.AreEqual CLng(SmoothingModeAntiAlias), CLng(g.SmoothingMode), "SmoothingMode after setting SmoothingModeHighQuality" + Expect.Done + End Sub + + [TestCase] + Public Sub SmoothingMode_HighSpeedAndDefault_ReadBackAsNone() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim g As Graphics = Graphics(Gr_White(4, 4)) + g.SmoothingMode = SmoothingModeHighSpeed + Expect.AreEqual CLng(SmoothingModeNone), CLng(g.SmoothingMode), "SmoothingMode after setting SmoothingModeHighSpeed" + g.SmoothingMode = SmoothingModeAntiAlias + g.SmoothingMode = SmoothingModeDefault + Expect.AreEqual CLng(SmoothingModeNone), CLng(g.SmoothingMode), "SmoothingMode after setting SmoothingModeDefault" + Expect.Done + End Sub + +End Module diff --git a/test/package-tests/GdiPlus/Sources/Gr_Helpers.twin b/test/package-tests/GdiPlus/Sources/Gr_Helpers.twin new file mode 100644 index 00000000..647edcc0 --- /dev/null +++ b/test/package-tests/GdiPlus/Sources/Gr_Helpers.twin @@ -0,0 +1,69 @@ +' Helpers of the graphics and library cases (Gr_*). Not a fixture: no case is in it. +Module Gr_Helpers + + ' A bitmap of the size, cleared to opaque white. + Public Function Gr_White(ByVal Width As Long, ByVal Height As Long) As Bitmap + Dim bmp As Bitmap = Bitmap(CSng(Width), CSng(Height)) + Dim g As Graphics = Graphics(bmp) + g.Clear Color(255, 255, 255, 255) + Return bmp + End Function + + ' A bitmap of the size, transparent. + Public Function Gr_Clear(ByVal Width As Long, ByVal Height As Long) As Bitmap + Return Bitmap(CSng(Width), CSng(Height)) + End Function + + ' The colour of a pixel of the bitmap, as &HAARRGGBB. + Public Function Gr_Px(ByVal Bmp As Bitmap, ByVal X As Long, ByVal Y As Long) As Long + Dim c As Color = Bmp.Pixel(X, Y) + Return c.argb + End Function + + ' The colour of a pixel, as text for a report. + Public Function Gr_Hex(ByVal Argb As Long) As String + Return Right$("00000000" & Hex$(Argb), 8) + End Function + + ' The number of pixels of the bitmap that are not the colour. + Public Function Gr_CountNot(ByVal Bmp As Bitmap, ByVal Argb As Long) As Long + Dim x As Long, y As Long, n As Long + For y = 0 To CLng(Bmp.Height) - 1 + For x = 0 To CLng(Bmp.Width) - 1 + If Gr_Px(Bmp, x, y) <> Argb Then n = n + 1 + Next + Next + Return n + End Function + + ' The first and last rows of the bitmap that hold a pixel of the colour; -1, -1 when there is none. + Public Sub Gr_Rows(ByVal Bmp As Bitmap, ByVal Argb As Long, ByRef First As Long, ByRef Last As Long) + Dim x As Long, y As Long + First = -1 + Last = -1 + For y = 0 To CLng(Bmp.Height) - 1 + For x = 0 To CLng(Bmp.Width) - 1 + If Gr_Px(Bmp, x, y) = Argb Then + If First = -1 Then First = y + Last = y + Exit For + End If + Next + Next + End Sub + + ' True when the two numbers differ by no more than the tolerance. + Public Function Gr_Near(ByVal Expected As Double, ByVal Actual As Double, ByVal Tolerance As Double) As Boolean + Return Abs(Expected - Actual) <= Tolerance + End Function + + ' The folder the cases write to: created when it is missing. + Public Function Gr_Folder() As String + Dim p As String = Environ$("TEMP") & "\tbdocs-package-tests" + If Dir$(p, vbDirectory) = "" Then MkDir p + p = p & "\GdiPlus" + If Dir$(p, vbDirectory) = "" Then MkDir p + Return p + End Function + +End Module diff --git a/test/package-tests/GdiPlus/Sources/Gr_LibraryTests.twin b/test/package-tests/GdiPlus/Sources/Gr_LibraryTests.twin new file mode 100644 index 00000000..e34ff79b --- /dev/null +++ b/test/package-tests/GdiPlus/Sources/Gr_LibraryTests.twin @@ -0,0 +1,246 @@ +' The library: GdiPlusUser, GdiPlusBase, GdiPlusAbort, the module functions, GdiplusStartupParams and GpStatus. +[TestFixture] +Module Gr_LibraryTests + + ' ---- GdiPlusUser + + [TestCase] + Public Sub GdiPlusUser_BitmapOfTwoWholeNumbers_IsCreated() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = Bitmap(64, 64) + Expect.AreEqual 0, CLng(bmp.LastResult), "LastResult of Bitmap(64, 64)" + Expect.AreEqual 64, CLng(bmp.Width), "Width of Bitmap(64, 64)" + Expect.AreEqual 64, CLng(bmp.Height), "Height of Bitmap(64, 64)" + Expect.Done + End Sub + + [TestCase] + Public Sub GdiPlusUser_BitmapOfSinglesOrWithFormat_IsCreated() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim a As Bitmap = Bitmap(64!, 64!) + Expect.AreEqual 0, CLng(a.LastResult), "LastResult of Bitmap(64!, 64!)" + Expect.AreEqual 64, CLng(a.Width), "Width of Bitmap(64!, 64!)" + Dim b As Bitmap = Bitmap(64, 64, PixelFormat32bppARGB) + Expect.AreEqual 0, CLng(b.LastResult), "LastResult of Bitmap(64, 64, PixelFormat32bppARGB)" + Expect.AreEqual 64, CLng(b.Height), "Height of Bitmap(64, 64, PixelFormat32bppARGB)" + Expect.Done + End Sub + + [TestCase] + Public Sub GdiPlusUser_InstanceWhileRunning_WithInvalidInput_ReportsFailure() + Expect.Begin + Dim first As GdiPlusUser = GdiPlusUser() + Expect.AreEqual 0, CLng(first.LastResult), "LastResult of the first GdiPlusUser" + Dim bad As GdiplusStartupInput + bad.GdiplusVersion = 99 + Dim second As GdiPlusUser = GdiPlusUser(bad) + Expect.AreEqual CLng(UnsupportedGdiplusVersion), CLng(second.LastResult), "LastResult of a second GdiPlusUser given GdiplusVersion 99 while the first is alive" + Expect.Done + End Sub + + [TestCase] + Public Sub GdiPlusUser_ReleasingTheLastInstance_KeepsGdiPlusRunning() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = Gr_White(8, 8) + Dim g As Graphics = Graphics(bmp) + Set user = Nothing + Expect.AreEqual 0, CLng(g.Clear(Color(255, 0, 0))), "status of Clear on a Graphics made before the last GdiPlusUser was released" + Expect.AreEqual "FFFF0000", Gr_Hex(Gr_Px(bmp, 1, 1)), "pixel after that Clear" + Dim later As Bitmap = Bitmap(8!, 8!) + Expect.AreEqual 0, CLng(later.LastResult), "LastResult of a Bitmap made after the last GdiPlusUser was released" + Dim again As GdiPlusUser = GdiPlusUser() + Expect.AreEqual 0, CLng(again.LastResult), "LastResult of a GdiPlusUser made after the last one was released" + Expect.Done + End Sub + + [TestCase] + Public Sub TestControlForceBilinear_Method_ReturnsOk() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Expect.AreEqual 0, CLng(user.TestControlForceBilinear(False)), "status of GdiPlusUser.TestControlForceBilinear(False)" + Expect.Done + End Sub + + [TestCase] + Public Sub TestControlNoICM_Method_ReturnsOk() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Expect.AreEqual 0, CLng(user.TestControlNoICM(False)), "status of GdiPlusUser.TestControlNoICM(False)" + Expect.Done + End Sub + + [TestCase] + Public Sub TestControl_ModuleFunctions_ReturnOk() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Expect.AreEqual 0, CLng(TestControlNoICM(False)), "status of the module function TestControlNoICM(False)" + Expect.AreEqual 0, CLng(TestControlForceBilinear(False)), "status of the module function TestControlForceBilinear(False)" + Expect.Done + End Sub + + ' ---- GdiPlusBase + + [TestCase] + Public Sub Alloc_ThroughAClass_ReturnsABlock() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim block As LongPtr = GdiPlusUser.Alloc(64) + Expect.IsTrue block <> 0, "GdiPlusUser.Alloc(64) returns a block" + If block <> 0 Then GdiPlusUser.Free block + Expect.Done + End Sub + + [TestCase] + Public Sub Image_AssignedToAVariableOfTheVbType_Fails() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = Gr_White(4, 4) + Dim img As Image + On Error Resume Next + Set img = bmp + Dim failed As Boolean = (Err.Number <> 0) Or (img Is Nothing) + On Error GoTo 0 + Expect.IsTrue failed, "assigning a Bitmap to a variable declared As Image (the control class of the VB package) raises an error or leaves Nothing" + Expect.Done + End Sub + + [TestCase] + Public Sub Image_AssignedToAGdiPlusImageVariable_IsUsable() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = Gr_White(4, 4) + Dim img As GdiPlus.Image = bmp + Expect.IsTrue Not (img Is Nothing), "a Bitmap assigned to a variable declared As GdiPlus.Image" + Dim named As GdiPlus.Image = Image(Gr_Folder() & "\no such file.png") + Expect.IsTrue Not (named Is Nothing), "the function Image(...) assigned to a GdiPlus.Image variable returns an object" + Expect.Done + End Sub + + ' ---- GdiPlusAbort + + [TestCase] + Public Sub SetAbort_ThenDrawingTheImage_Works() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim src As Bitmap = Gr_White(4, 4) + Dim stopper As Gr_NeverAbort = New Gr_NeverAbort + Expect.AreEqual 0, CLng(src.SetAbort(stopper)), "status of Bitmap.SetAbort with a GdiPlusAbort that never asks to stop" + Dim dst As Bitmap = Gr_Clear(8, 8) + Dim g As Graphics = Graphics(dst) + Expect.AreEqual 0, CLng(g.DrawImage(src, 0, 0, 4, 4)), "status of DrawImage of the image that was given the abort object" + Expect.AreEqual "FFFFFFFF", Gr_Hex(Gr_Px(dst, 1, 1)), "pixel of the drawn image" + Expect.Done + End Sub + + ' ---- the module functions + + [TestCase] + Public Sub IsCanonicalPixelFormat_PremultipliedFormats_AreCanonical() + Expect.Begin + Expect.IsTrue IsCanonicalPixelFormat(PixelFormat32bppARGB), "32bppARGB is canonical" + Expect.IsTrue IsCanonicalPixelFormat(PixelFormat64bppARGB), "64bppARGB is canonical" + Expect.IsTrue IsCanonicalPixelFormat(PixelFormat32bppPARGB), "32bppPARGB is canonical" + Expect.IsTrue IsCanonicalPixelFormat(PixelFormat64bppPARGB), "64bppPARGB is canonical" + Expect.IsFalse IsCanonicalPixelFormat(PixelFormat24bppRGB), "24bppRGB is not canonical" + Expect.Done + End Sub + + [TestCase] + Public Sub GdipCreateStreamOnFile_ExistingFile_ReturnsAStream() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim path As String = Gr_Folder() & "\Gr_stream.tmp" + Dim f As Integer = FreeFile + Open path For Binary As #f + Put #f, , CByte(65) + Close #f + Dim stream As GDIP_IStream + Dim st As Long = GdipCreateStreamOnFile(StrPtr(path), &H80000000, stream) + Expect.AreEqual 0, st, "status of GdipCreateStreamOnFile for an existing file, opened for reading" + Expect.IsTrue Not (stream Is Nothing), "GdipCreateStreamOnFile stores a stream" + Set stream = Nothing + Kill path + Expect.Done + End Sub + + [TestCase] + Public Sub PropertyTag_ConstantsAbove8000_AreNotNegativeInLong() + Expect.Begin + Dim tag As Long = PropertyTagCopyright + Expect.AreEqual &H8298&, tag, "PropertyTagCopyright assigned to a Long, expected 33432 (&H8298)" + Dim exif As Long = PropertyTagExifIFD + Expect.AreEqual &H8769&, exif, "PropertyTagExifIFD assigned to a Long, expected 34665 (&H8769)" + Dim small As Long = PropertyTagImageDescription + Expect.AreEqual &H10E&, small, "PropertyTagImageDescription assigned to a Long, expected 270 (&H10E)" + Expect.Done + End Sub + + [TestCase] + Public Sub RemovePropertyItem_HighPropertyTag_RemovesTheItem() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = Bitmap(4!, 4!) + Dim text() As Byte = StrConv("(c) test" & vbNullChar, vbFromUnicode) + Dim item As GpPropertyItem + item.Id = &H8298& + item._type = PropertyTagTypeASCII + item._length = UBound(text) + 1 + item._address = VarPtr(text(0)) + bmp.PropertyItem = item + Expect.AreEqual 1, CLng(bmp.PropertyCount), "PropertyCount after setting the property &H8298" + Expect.AreEqual 0, CLng(bmp.RemovePropertyItem(PropertyTagCopyright)), "status of RemovePropertyItem(PropertyTagCopyright)" + Expect.AreEqual 0, CLng(bmp.PropertyCount), "PropertyCount after RemovePropertyItem(PropertyTagCopyright)" + Expect.Done + End Sub + + [TestCase] + Public Sub RemovePropertyItem_HighPropertyTagConvertedToLong_RemovesTheItem() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = Bitmap(4!, 4!) + Dim text() As Byte = StrConv("(c) test" & vbNullChar, vbFromUnicode) + Dim item As GpPropertyItem + item.Id = &H8298& + item._type = PropertyTagTypeASCII + item._length = UBound(text) + 1 + item._address = VarPtr(text(0)) + bmp.PropertyItem = item + Expect.AreEqual 0, CLng(bmp.RemovePropertyItem(PropertyTagCopyright And &HFFFF&)), "status of RemovePropertyItem(PropertyTagCopyright And &HFFFF&)" + Expect.AreEqual 0, CLng(bmp.PropertyCount), "PropertyCount after RemovePropertyItem(PropertyTagCopyright And &HFFFF&)" + Expect.Done + End Sub + + ' ---- the enumerations + + [TestCase] + Public Sub GdiplusStartupParams_ReservedMembers_AreSingleBits() + Expect.Begin + Dim v1 As Long = GdiplusStartupReserved1 + Dim v2 As Long = GdiplusStartupReserved2 + Expect.IsTrue v1 <> 0 And (v1 And (v1 - 1)) = 0, "GdiplusStartupReserved1 is a single bit, as a member of a flags enumeration is" + Expect.IsTrue v2 <> 0 And (v2 And (v2 - 1)) = 0, "GdiplusStartupReserved2 is a single bit, as a member of a flags enumeration is" + Expect.Done + End Sub + + [TestCase] + Public Sub GdiplusStartupParams_ReservedMembers_FollowReserved0() + Expect.Begin + Expect.AreEqual 4, CLng(GdiplusStartupReserved0), "GdiplusStartupReserved0" + Expect.AreEqual 5, CLng(GdiplusStartupReserved1), "GdiplusStartupReserved1, which has no value of its own" + Expect.AreEqual 6, CLng(GdiplusStartupReserved2), "GdiplusStartupReserved2, which has no value of its own" + Expect.Done + End Sub + + [TestCase] + Public Sub GpStatus_ImageFromMissingFile_IsInvalidParameter() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = Bitmap(Gr_Folder() & "\no such file.png") + Expect.AreEqual CLng(InvalidParameter), CLng(bmp.LastResult), "LastResult of a Bitmap made from a file that does not exist" + Expect.Done + End Sub + +End Module diff --git a/test/package-tests/GdiPlus/Sources/Gr_MatrixTests.twin b/test/package-tests/GdiPlus/Sources/Gr_MatrixTests.twin new file mode 100644 index 00000000..90642fb9 --- /dev/null +++ b/test/package-tests/GdiPlus/Sources/Gr_MatrixTests.twin @@ -0,0 +1,179 @@ +' Matrix and the ITransformable interface it shares with Graphics. +[TestFixture] +Module Gr_MatrixTests + + ' ---- the rectangle and point constructors + + [TestCase] + Public Sub Matrix_RectFAndPoint_MovesTheRectangleToThePoint() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim m As Matrix = Matrix(GpRectF(0, 0, 10, 10), GpPointF(5, 5)) + Dim pts(0 To 1) As GpPointF + pts(0) = GpPointF(0, 0) + pts(1) = GpPointF(10, 10) + m.TransformPoints pts + Dim a As GpPointF = pts(0) + Dim b As GpPointF = pts(1) + Expect.IsTrue Gr_Near(5, a.X, 0.01) And Gr_Near(5, a.Y, 0.01), "the top-left corner of the rectangle maps to the point (5, 5)" + Expect.IsTrue Gr_Near(15, b.X, 0.01) And Gr_Near(15, b.Y, 0.01), "the bottom-right corner of the 10 by 10 rectangle maps to (15, 15)" + Expect.Done + End Sub + + [TestCase] + Public Sub Matrix_RectAndPoint_MovesTheRectangleToThePoint() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim m As Matrix = Matrix(GpRect(0, 0, 10, 10), GpPoint(5, 5)) + Dim pts(0 To 1) As GpPointF + pts(0) = GpPointF(0, 0) + pts(1) = GpPointF(10, 10) + m.TransformPoints pts + Dim a As GpPointF = pts(0) + Dim b As GpPointF = pts(1) + Expect.IsTrue Gr_Near(5, a.X, 0.01) And Gr_Near(5, a.Y, 0.01), "the top-left corner of the rectangle maps to the point (5, 5)" + Expect.IsTrue Gr_Near(15, b.X, 0.01) And Gr_Near(15, b.Y, 0.01), "the bottom-right corner of the 10 by 10 rectangle maps to (15, 15)" + Expect.Done + End Sub + + ' ---- the offsets + + [TestCase] + Public Sub OffsetX_Assign_ChangesOnlyDx() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim m As Matrix = Matrix(1, 0, 0, 1, 3, 4) + m.OffsetX = 7 + Expect.AreEqual 7, CLng(m.OffsetX), "OffsetX after assigning 7" + Expect.AreEqual 4, CLng(m.OffsetY), "OffsetY after assigning 7 to OffsetX" + Expect.Done + End Sub + + [TestCase] + Public Sub OffsetY_Assign_ChangesOnlyDy() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim m As Matrix = Matrix(1, 0, 0, 1, 3, 4) + m.OffsetY = 9 + Expect.AreEqual 3, CLng(m.OffsetX), "OffsetX after assigning 9 to OffsetY" + Expect.AreEqual 9, CLng(m.OffsetY), "OffsetY after assigning 9" + Expect.Done + End Sub + + [TestCase] + Public Sub Offset_Assign_ChangesBoth() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim m As Matrix = Matrix(1, 0, 0, 1, 3, 4) + m.Offset = GpPointF(m.OffsetX, 9) + Expect.AreEqual 3, CLng(m.OffsetX), "OffsetX after assigning Offset" + Expect.AreEqual 9, CLng(m.OffsetY), "OffsetY after assigning Offset" + Expect.Done + End Sub + + ' ---- Scale as a statement + + [TestCase] + Public Sub Scale_StatementWithParentheses_Scales() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim m As Matrix = Matrix() + m.Scale(2, 3) + Dim e() As Single = m.Elements + Expect.AreEqual 2, CLng(e(1)), "m11 after the statement m.Scale(2, 3) (a defect of twinBASIC BETA 1005, not of the package)" + Expect.AreEqual 3, CLng(e(4)), "m22 after the statement m.Scale(2, 3) (a defect of twinBASIC BETA 1005, not of the package)" + Expect.Done + End Sub + + [TestCase] + Public Sub Scale_OtherSpellings_Scale() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim a As Matrix = Matrix() + a.Scale 2, 3 + Dim ea() As Single = a.Elements + Expect.IsTrue ea(1) = 2 And ea(4) = 3, "the statement m.Scale 2, 3 scales" + Dim b As Matrix = Matrix() + Call b.Scale(2, 3) + Dim eb() As Single = b.Elements + Expect.IsTrue eb(1) = 2 And eb(4) = 3, "the statement Call m.Scale(2, 3) scales" + Dim c As Matrix = Matrix() + Dim st As Long = c.Scale(2, 3) + Dim ec() As Single = c.Elements + Expect.IsTrue ec(1) = 2 And ec(4) = 3 And st = 0, "status = m.Scale(2, 3) scales and returns Ok" + Expect.Done + End Sub + + ' ---- ITransformable + + [TestCase] + Public Sub Transform_GetOnMatrix_ReturnsTheMatrix() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim m As Matrix = Matrix(2, 0, 0, 3, 10, 20) + Dim t As ITransformable = m + m.ClearResult + Dim copy As Matrix = t.Transform + Expect.IsTrue Not (copy Is Nothing), "Transform read through ITransformable on a Matrix returns a Matrix" + If Not (copy Is Nothing) Then + Dim e() As Single = copy.Elements + Expect.IsTrue e(1) = 2 And e(4) = 3 And e(5) = 10 And e(6) = 20, "the elements of the matrix that Transform returns" + End If + Expect.AreEqual 0, CLng(m.LastResult), "LastResult of the Matrix after reading Transform" + Expect.Done + End Sub + + [TestCase] + Public Sub Transform_GetOnGraphics_ThroughTheInterface_ReturnsTheMatrix() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim g As Graphics = Graphics(Gr_White(10, 10)) + g.TranslateTransform 7, 8 + Dim t As ITransformable = g + g.ClearResult + Dim copy As Matrix = t.Transform + Expect.IsTrue Not (copy Is Nothing), "Transform read through ITransformable on a Graphics returns a Matrix" + Expect.AreEqual 0, CLng(g.LastResult), "LastResult of the Graphics after reading Transform" + Expect.Done + End Sub + + [TestCase] + Public Sub Transform_GetOnGradientBrush_ThroughTheInterface_ReturnsTheMatrix() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim brush As LinearGradientBrush = LinearGradientBrush(GpRectF(0, 0, 10, 10), Color(255, 0, 0), Color(0, 0, 255)) + Dim t As ITransformable = brush + brush.ClearResult + Dim copy As Matrix = t.Transform + Expect.IsTrue Not (copy Is Nothing), "Transform read through ITransformable on a LinearGradientBrush returns a Matrix" + Expect.AreEqual 0, CLng(brush.LastResult), "LastResult of the brush after reading Transform" + Expect.Done + End Sub + + [TestCase] + Public Sub Transform_Let_CopiesTheMatrix() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim m As Matrix = Matrix() + Dim t As ITransformable = m + t.Transform = Matrix(3, 0, 0, 3, 0, 0) + Dim e() As Single = m.Elements + Expect.IsTrue e(1) = 3 And e(4) = 3, "the elements of a matrix after assigning Transform" + Expect.Done + End Sub + + [TestCase] + Public Sub GetTransform_OnGraphics_ThroughTheInterface_CopiesTheTransformation() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim g As Graphics = Graphics(Gr_White(10, 10)) + Dim t As ITransformable = g + t.TranslateTransform 7, 8 + Dim w As Matrix = Matrix() + Expect.AreEqual 0, CLng(t.GetTransform(w)), "status of GetTransform through ITransformable" + Expect.AreEqual 7, CLng(w.OffsetX), "OffsetX of the copied transformation" + Expect.AreEqual 8, CLng(w.OffsetY), "OffsetY of the copied transformation" + Expect.Done + End Sub + +End Module diff --git a/test/package-tests/GdiPlus/Sources/Gr_NeverAbort.twin b/test/package-tests/GdiPlus/Sources/Gr_NeverAbort.twin new file mode 100644 index 00000000..48ee862f --- /dev/null +++ b/test/package-tests/GdiPlus/Sources/Gr_NeverAbort.twin @@ -0,0 +1,8 @@ +' An implementation of GdiPlusAbort that never asks GDI+ to stop. +Class Gr_NeverAbort + Implements GdiPlusAbort + + Private Function GdiPlusAbort_Abort() As Long Implements GdiPlusAbort.Abort + Return 0 + End Function +End Class diff --git a/test/package-tests/GdiPlus/Sources/Gr_VBGraphicsTests.twin b/test/package-tests/GdiPlus/Sources/Gr_VBGraphicsTests.twin new file mode 100644 index 00000000..b65684a0 --- /dev/null +++ b/test/package-tests/GdiPlus/Sources/Gr_VBGraphicsTests.twin @@ -0,0 +1,184 @@ +' VBGraphics: the Line, Circle and PSet statements and the properties they read. +[TestFixture] +Module Gr_VBGraphicsTests + + Private Const Red As Long = &HFFFF0000 + Private Const White As Long = &HFFFFFFFF + + Private Function Canvas(ByRef Gfx As VBGraphics) As Bitmap + Dim bmp As Bitmap = Bitmap(40!, 40!) + Set Gfx = VBGraphics(bmp) + Gfx.Clear Color(255, 255, 255, 255) + Return bmp + End Function + + ' ---- colours + + [TestCase] + Public Sub Line_ARGBColour_Draws() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim g As VBGraphics + Dim bmp As Bitmap = Canvas(g) + g.Line (2, 5)-(30, 5), &HFFFF0000 + Expect.AreEqual "FFFF0000", Gr_Hex(Gr_Px(bmp, 10, 5)), "pixel on a line drawn in &HFFFF0000" + Expect.AreEqual "FFFFFFFF", Gr_Hex(Gr_Px(bmp, 10, 8)), "pixel away from the line" + Expect.Done + End Sub + + [TestCase] + Public Sub Line_VBColourRed_DrawsRed() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim g As VBGraphics + Dim bmp As Bitmap = Canvas(g) + g.Line (2, 5)-(30, 5), vbRed + Expect.AreEqual "FFFF0000", Gr_Hex(Gr_Px(bmp, 10, 5)), "pixel on a line drawn in vbRed with the default UseTransparentAlpha" + Expect.Done + End Sub + + [TestCase] + Public Sub Line_VBColourWithoutTransparentAlpha_DrawsThatColour() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim g As VBGraphics + Dim bmp As Bitmap = Canvas(g) + g.UseTransparentAlpha = False + g.Line (2, 5)-(30, 5), vbRed + Expect.AreEqual "FFFF0000", Gr_Hex(Gr_Px(bmp, 10, 5)), "pixel on a line drawn in vbRed with UseTransparentAlpha False" + g.Line (2, 9)-(30, 9), RGB(10, 20, 30) + Expect.AreEqual "FF0A141E", Gr_Hex(Gr_Px(bmp, 10, 9)), "pixel on a line drawn in RGB(10, 20, 30) with UseTransparentAlpha False" + Expect.Done + End Sub + + [TestCase] + Public Sub Line_AlphaZeroColour_IsInvisibleWithTransparentAlpha() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim g As VBGraphics + Dim bmp As Bitmap = Canvas(g) + g.Line (2, 5)-(30, 5), &HFF0000 + Expect.AreEqual "FFFFFFFF", Gr_Hex(Gr_Px(bmp, 10, 5)), "pixel on a line drawn in &HFF0000 (alpha 0) with the default UseTransparentAlpha" + g.UseTransparentAlpha = False + g.Line (2, 9)-(30, 9), &HFF0000 + Expect.AreEqual "FFFF0000", Gr_Hex(Gr_Px(bmp, 10, 9)), "pixel on a line drawn in &HFF0000 (alpha 0) with UseTransparentAlpha False" + Expect.Done + End Sub + + ' ---- DrawWidth + + [TestCase] + Public Sub DrawWidth_BeforeFirstDraw_ReadsOne() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim g As VBGraphics + Dim bmp As Bitmap = Canvas(g) + g.ClearResult + Expect.AreEqual 1, CLng(g.DrawWidth), "DrawWidth of a new VBGraphics" + Expect.AreEqual 0, CLng(g.LastResult), "LastResult after reading DrawWidth of a new VBGraphics" + Expect.Done + End Sub + + [TestCase] + Public Sub DrawWidth_AssignedBeforeFirstDraw_IsKept() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim g As VBGraphics + Dim bmp As Bitmap = Canvas(g) + g.ClearResult + g.DrawWidth = 4 + Expect.AreEqual 4, CLng(g.DrawWidth), "DrawWidth after assigning 4 to a new VBGraphics" + Expect.AreEqual 0, CLng(g.LastResult), "LastResult after assigning DrawWidth of a new VBGraphics" + Expect.Done + End Sub + + [TestCase] + Public Sub DrawWidth_AfterFirstDraw_CanBeChanged() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim g As VBGraphics + Dim bmp As Bitmap = Canvas(g) + g.PSet (10, 10), &HFFFF0000 + g.ClearResult + Expect.AreEqual 1, CLng(g.DrawWidth), "DrawWidth after a first PSet" + g.DrawWidth = 4 + Expect.AreEqual 4, CLng(g.DrawWidth), "DrawWidth after assigning 4 once the first PSet is done" + Expect.Done + End Sub + + ' ---- Line with B and BF + + [TestCase] + Public Sub Line_BoxFilled_FillsFromTheFirstCorner() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim g As VBGraphics + Dim bmp As Bitmap = Canvas(g) + g.Line (5, 5)-(15, 15), &HFFFF0000, BF + Expect.AreEqual "FFFF0000", Gr_Hex(Gr_Px(bmp, 10, 10)), "pixel inside the box (5, 5)-(15, 15) drawn with BF" + Expect.AreEqual "FFFFFFFF", Gr_Hex(Gr_Px(bmp, 10, 22)), "pixel below the box" + Dim first As Long, last As Long + Gr_Rows bmp, Red, first, last + Expect.AreEqual 5, first, "first row with red pixels after BF (5, 5)-(15, 15)" + Expect.AreEqual 15, last, "last row with red pixels after BF (5, 5)-(15, 15)" + Expect.Done + End Sub + + [TestCase] + Public Sub Line_BoxOutline_DrawsTheCorners() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim g As VBGraphics + Dim bmp As Bitmap = Canvas(g) + g.Line (5, 5)-(15, 15), &HFFFF0000, B + Expect.AreEqual "FFFF0000", Gr_Hex(Gr_Px(bmp, 10, 5)), "pixel on the top edge of the box" + Expect.AreEqual "FFFF0000", Gr_Hex(Gr_Px(bmp, 10, 15)), "pixel on the bottom edge of the box" + Expect.AreEqual "FFFFFFFF", Gr_Hex(Gr_Px(bmp, 10, 10)), "pixel inside an unfilled box" + Expect.Done + End Sub + + ' ---- Circle + + [TestCase] + Public Sub Circle_StartAndEnd_DrawAnArc() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim g As VBGraphics + Dim bmp As Bitmap = Canvas(g) + g.ClearResult + g.Circle (20, 20), 8, &HFFFF0000, 0, 3.14 + Expect.AreEqual 0, CLng(g.LastResult), "LastResult after Circle with start and end" + Expect.IsTrue Gr_CountNot(bmp, White) > 0, "pixels drawn by Circle with start 0 and end 3.14" + Expect.Done + End Sub + + [TestCase] + Public Sub Circle_Full_DrawsTheOutline() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim g As VBGraphics + Dim bmp As Bitmap = Canvas(g) + g.Circle (20, 20), 8, &HFFFF0000 + Expect.AreEqual "FFFF0000", Gr_Hex(Gr_Px(bmp, 20, 12)), "pixel on the top of the circle" + Expect.AreEqual "FFFF0000", Gr_Hex(Gr_Px(bmp, 28, 20)), "pixel on the right of the circle" + Expect.AreEqual "FFFFFFFF", Gr_Hex(Gr_Px(bmp, 20, 20)), "pixel in the centre of an unfilled circle" + Expect.Done + End Sub + + ' ---- PSet + + [TestCase] + Public Sub PSet_SetsOnlyThePixel() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim g As VBGraphics + Dim bmp As Bitmap = Canvas(g) + g.PSet (10, 10), &HFFFF0000 + Expect.AreEqual "FFFF0000", Gr_Hex(Gr_Px(bmp, 10, 10)), "pixel at the PSet point" + Expect.AreEqual "FFFFFFFF", Gr_Hex(Gr_Px(bmp, 10, 11)), "pixel below the PSet point" + Expect.AreEqual "FFFFFFFF", Gr_Hex(Gr_Px(bmp, 10, 9)), "pixel above the PSet point" + Expect.AreEqual "FFFFFFFF", Gr_Hex(Gr_Px(bmp, 11, 10)), "pixel right of the PSet point" + Expect.Done + End Sub + +End Module diff --git a/test/package-tests/GdiPlus/Sources/Im_AttributeTests.twin b/test/package-tests/GdiPlus/Sources/Im_AttributeTests.twin new file mode 100644 index 00000000..0a038b67 --- /dev/null +++ b/test/package-tests/GdiPlus/Sources/Im_AttributeTests.twin @@ -0,0 +1,96 @@ +' GdiPlus, ImageAttributes: the colour matrix indexes and the colour key. Each case draws a +' one-pixel picture of the colour 200, 100, 50 through the attributes onto a black pixel. +[TestFixture] +Module Im_AttributeTests + + ' The pixel that results from drawing the picture through attrs onto black. + Private Function DrawThrough(attrs As ImageAttributes) As Long + Dim src As Bitmap = Bitmap(1!, 1!) + src.Pixel(0, 0) = Color(255, 200, 100, 50) + Dim dst As Bitmap = Bitmap(1!, 1!) + Dim g As Graphics = Graphics(dst) + g.Clear Color(255, 0, 0, 0) + g.DrawImage src, GpRect(0, 0, 1, 1), GpRect(0, 0, 1, 1), UnitPixel, attrs + Return dst.Pixel(0, 0).argb + End Function + + Private Function Near(ByVal expected As Long, ByVal actual As Long) As Boolean + Dim channel As Long + For channel = 0 To 24 Step 8 + If Abs(((expected >> channel) And &HFF) - ((actual >> channel) And &HFF)) > 1 Then Return False + Next + Return True + End Function + + [TestCase] + Public Sub ColorMatrix_FirstIndexIsTheOutputComponent() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim cm As ColorMatrix + Dim i As Long + For i = 0 To 4 + cm.m(i, i) = 1 + Next + cm.m(2, 0) = 1 ' blue += red + Dim attrs As ImageAttributes = ImageAttributes() + Expect.AreEqual 0, CLng(attrs.SetColorMatrix(cm)), "SetColorMatrix returns (0 is Ok)" + Dim px As Long = DrawThrough(attrs) + Expect.IsTrue Near(&HFFC864FA, px), "the pixel after m(2, 0) = 1 adds the red of the input to the blue of the output: expected FFC864FA, got " & Hex$(px) + + cm.m(2, 0) = 0 + cm.m(0, 2) = 1 ' red += blue + Set attrs = ImageAttributes() + attrs.SetColorMatrix cm + px = DrawThrough(attrs) + Expect.IsTrue Near(&HFFFA6432, px), "the pixel after m(0, 2) = 1 adds the blue of the input to the red of the output: expected FFFA6432, got " & Hex$(px) + Expect.Done + End Sub + + [TestCase] + Public Sub ColorMatrix_FifthColumnAddsAConstant() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim cm As ColorMatrix + Dim i As Long + For i = 0 To 4 + cm.m(i, i) = 1 + Next + cm.m(1, 4) = 0.1! ' green += 0.1 of the range + Dim attrs As ImageAttributes = ImageAttributes() + attrs.SetColorMatrix cm + Dim px As Long = DrawThrough(attrs) + Expect.IsTrue Near(&HFFC87E32, px), "the pixel after m(1, 4) = 0.1 adds a tenth of the range to the green: expected FFC87E32, got " & Hex$(px) + Expect.Done + End Sub + + [TestCase] + Public Sub ColorKey_ColorFormKeysOutTheColour() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim attrs As ImageAttributes = ImageAttributes() + attrs.SetColorKey Color(255, 200, 100, 50), Color(255, 200, 100, 50) + Expect.AreEqual &HFF000000, DrawThrough(attrs), "the pixel of the colour 200, 100, 50 after SetColorKey with that Color twice (black means keyed out)" + Expect.Done + End Sub + + [TestCase] + Public Sub ColorKey_LongFormTakesAVbColour() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim attrs As ImageAttributes = ImageAttributes() + attrs.SetColorKey RGB(200, 100, 50), RGB(200, 100, 50) + Expect.AreEqual &HFF000000, DrawThrough(attrs), "the pixel of the colour 200, 100, 50 after SetColorKey with RGB(200, 100, 50) twice (black means keyed out; the value is a VB colour)" + Expect.Done + End Sub + + [TestCase] + Public Sub ColorKey_LongFormTakesRrggbbAsDocumented() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim attrs As ImageAttributes = ImageAttributes() + attrs.SetColorKey &HC86432, &HC86432 + Expect.AreEqual &HFF000000, DrawThrough(attrs), "the pixel of the colour 200, 100, 50 after SetColorKey with &HC86432 (&HRRGGBB) twice (black means keyed out)" + Expect.Done + End Sub + +End Module diff --git a/test/package-tests/GdiPlus/Sources/Im_BitmapTests.twin b/test/package-tests/GdiPlus/Sources/Im_BitmapTests.twin new file mode 100644 index 00000000..04b08ce3 --- /dev/null +++ b/test/package-tests/GdiPlus/Sources/Im_BitmapTests.twin @@ -0,0 +1,152 @@ +' GdiPlus, Bitmap: constructors, ConvertFormat and loading a saved picture. +[TestFixture] +Module Im_BitmapTests + + [TestCase] + Public Sub New_TwoIntegerArgumentsMakeABitmap() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = Bitmap(8, 6) + Expect.AreEqual 0, CLng(bmp.LastResult), "LastResult of Bitmap(8, 6) with two Integer arguments (0 is Ok, 7 is Win32Error)" + Expect.AreEqual 8, CLng(bmp.Width), "Width of Bitmap(8, 6)" + Expect.AreEqual 6, CLng(bmp.Height), "Height of Bitmap(8, 6)" + Expect.Done + End Sub + + [TestCase] + Public Sub New_NewKeywordWithTwoIntegerArguments() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = New Bitmap(8, 6) + Expect.AreEqual 0, CLng(bmp.LastResult), "LastResult of New Bitmap(8, 6) with two Integer arguments (0 is Ok, 7 is Win32Error)" + Expect.AreEqual 8, CLng(bmp.Width), "Width of New Bitmap(8, 6)" + Expect.Done + End Sub + + [TestCase] + Public Sub New_SingleArgumentsMakeABitmapOfThatSize() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = Bitmap(8!, 6!) + Expect.AreEqual 0, CLng(bmp.LastResult), "LastResult of Bitmap(8!, 6!) (0 is Ok)" + Expect.AreEqual 8, CLng(bmp.Width), "Width of Bitmap(8!, 6!)" + Expect.AreEqual 6, CLng(bmp.Height), "Height of Bitmap(8!, 6!)" + Dim withFormat As Bitmap = Bitmap(8, 6, PixelFormat24bppRGB) + Expect.AreEqual 0, CLng(withFormat.LastResult), "LastResult of Bitmap(8, 6, PixelFormat24bppRGB) (0 is Ok)" + Expect.AreEqual 8, CLng(withFormat.Width), "Width of Bitmap(8, 6, PixelFormat24bppRGB)" + Expect.Done + End Sub + + [TestCase] + Public Sub Clone_WholeBitmapWithItsRectangleKeepsPixels() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = Bitmap(4!, 3!) + bmp.Pixel(1, 1) = Color(255, 60, 100, 7) + Dim whole As Bitmap = bmp.Clone(GpRect(0, 0, 4, 3), PixelFormat32bppARGB) + Expect.AreEqual 0, CLng(bmp.LastResult), "LastResult of Bitmap.Clone with the full rectangle (0 is Ok)" + Expect.AreEqual 4, CLng(whole.Width), "Width of the copy" + Expect.AreEqual &HFF3C6407, whole.Pixel(1, 1).argb, "the pixel of the copy" + Expect.Done + End Sub + + [TestCase] + Public Sub ConvertFormat_FixedBWWithABlankPalette() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = Bitmap(4!, 4!) + Dim g As Graphics = Graphics(bmp) + g.Clear Color(255, 255, 255, 255) + Set g = Nothing + Dim blank As ColorPalette + Dim st As GpStatus = bmp.ConvertFormat(PixelFormat1bppIndexed, DitherTypeSolid, PaletteTypeFixedBW, blank, 0!) + Expect.AreEqual 0, CLng(st), "ConvertFormat to 1 bit with PaletteTypeFixedBW and a blank ColorPalette returns (0 is Ok)" + Expect.AreEqual &H30101, CLng(bmp.PixelFormat), "the pixel format after the conversion with PaletteTypeFixedBW and a blank palette" + Expect.AreEqual &HFFFFFFFF, bmp.Pixel(2, 2).argb, "the white pixel after the conversion with PaletteTypeFixedBW and a blank palette" + Expect.Done + End Sub + + [TestCase] + Public Sub ConvertFormat_OptimalWithABlankPalette() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = Bitmap(4!, 4!) + Dim g As Graphics = Graphics(bmp) + g.Clear Color(255, 200, 100, 50) + Set g = Nothing + Dim blank As ColorPalette + blank.count = 16 + Dim st As GpStatus = bmp.ConvertFormat(PixelFormat8bppIndexed, DitherTypeSolid, PaletteTypeOptimal, blank, 0!) + Expect.AreEqual 0, CLng(st), "ConvertFormat to 8 bits with PaletteTypeOptimal and a blank ColorPalette of 16 entries returns (0 is Ok)" + Expect.AreEqual &HFFC86432, bmp.Pixel(1, 1).argb, "the pixel of a bitmap of one colour after the conversion with PaletteTypeOptimal and a blank palette" + Expect.Done + End Sub + + [TestCase] + Public Sub ConvertFormat_FixedPaletteTypeWithAFilledPalette() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = Bitmap(4!, 4!) + Dim g As Graphics = Graphics(bmp) + g.Clear Color(255, 255, 255, 255) + Set g = Nothing + bmp.Pixel(0, 0) = Color(255, 0, 0, 0) + Dim pal As ColorPalette + pal.flags = PaletteFlagsGrayScale + pal.count = 2 + pal.entries(0) = &HFF000000 + pal.entries(1) = &HFFFFFFFF + Dim st As GpStatus = bmp.ConvertFormat(PixelFormat1bppIndexed, DitherTypeSolid, PaletteTypeFixedBW, pal, 0!) + Expect.AreEqual 0, CLng(st), "ConvertFormat to 1 bit with PaletteTypeFixedBW and a palette of two colours returns (0 is Ok)" + Expect.AreEqual &H30101, CLng(bmp.PixelFormat), "the pixel format after the conversion with a filled-in palette" + Expect.AreEqual &HFF000000, bmp.Pixel(0, 0).argb, "the black pixel after the conversion with a filled-in palette" + Expect.AreEqual &HFFFFFFFF, bmp.Pixel(2, 2).argb, "the white pixel after the conversion with a filled-in palette" + Expect.Done + End Sub + + [TestCase] + Public Sub ConvertFormat_CustomPaletteFilledByTheProgram() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = Bitmap(4!, 4!) + Dim g As Graphics = Graphics(bmp) + g.Clear Color(255, 255, 255, 255) + bmp.Pixel(0, 0) = Color(255, 0, 0, 0) + Set g = Nothing + Dim pal As ColorPalette + pal.flags = PaletteFlagsGrayScale + pal.count = 2 + pal.entries(0) = &HFF000000 + pal.entries(1) = &HFFFFFFFF + Dim st As GpStatus = bmp.ConvertFormat(PixelFormat1bppIndexed, DitherTypeSolid, PaletteTypeCustom, pal, 0!) + Expect.AreEqual 0, CLng(st), "ConvertFormat to 1 bit with PaletteTypeCustom and a palette of two colours returns (0 is Ok)" + Expect.AreEqual &H30101, CLng(bmp.PixelFormat), "the pixel format after the conversion" + Expect.AreEqual &HFF000000, bmp.Pixel(0, 0).argb, "the black pixel after the conversion" + Expect.AreEqual &HFFFFFFFF, bmp.Pixel(2, 2).argb, "a white pixel after the conversion" + Expect.Done + End Sub + + ' ---- Saving a PNG without Image.Save + + [TestCase] + Public Sub Load_PngWrittenThroughTheNativeCall() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim path As String = Im_Dir() & "\native.png" + Dim bmp As Bitmap = Bitmap(16!, 8!) + Dim g As Graphics = Graphics(bmp) + g.Clear Color(255, 200, 100, 50) + Set g = Nothing + Expect.AreEqual 0, Im_SavePng(bmp, path), "saving a PNG through GdipSaveImageToFile with no parameters returns (0 is Ok)" + Dim loaded As Bitmap = Bitmap(path) + Expect.AreEqual 0, CLng(loaded.LastResult), "LastResult of Bitmap(filename) of the PNG (0 is Ok)" + Expect.AreEqual 16, CLng(loaded.Width), "Width of the PNG that was written" + Expect.AreEqual 8, CLng(loaded.Height), "Height of the PNG that was written" + Expect.AreEqual &HFFC86432, loaded.Pixel(3, 3).argb, "the pixel of the PNG that was written" + Set loaded = Nothing + Set bmp = Nothing + Im_Clean + Expect.Done + End Sub + +End Module diff --git a/test/package-tests/GdiPlus/Sources/Im_CachedBitmapTests.twin b/test/package-tests/GdiPlus/Sources/Im_CachedBitmapTests.twin new file mode 100644 index 00000000..eb8a93e3 --- /dev/null +++ b/test/package-tests/GdiPlus/Sources/Im_CachedBitmapTests.twin @@ -0,0 +1,42 @@ +' GdiPlus, CachedBitmap: a copy is valid for the Graphics it was made for and for a Graphics +' of the same pixel format. +[TestFixture] +Module Im_CachedBitmapTests + + [TestCase] + Public Sub Draw_OnGraphicsOfTheSameFormat() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim tile As Bitmap = Bitmap(4!, 4!) + Dim tg As Graphics = Graphics(tile) + tg.Clear Color(255, 255, 0, 0) + Dim first As Bitmap = Bitmap(10!, 10!) + Dim second As Bitmap = Bitmap(10!, 10!) + Dim g1 As Graphics = Graphics(first) + Dim g2 As Graphics = Graphics(second) + Dim cached As CachedBitmap = CachedBitmap(tile, g1) + Expect.AreEqual 0, CLng(cached.LastResult), "LastResult of CachedBitmap(tile, g) (0 is Ok)" + Expect.AreEqual 0, CLng(g1.DrawCachedBitmapI(cached, 2, 2)), "DrawCachedBitmapI on the Graphics it was made for returns (0 is Ok)" + Expect.AreEqual &HFFFF0000, first.Pixel(3, 3).argb, "the pixel drawn on the Graphics it was made for" + Expect.AreEqual 0, CLng(g2.DrawCachedBitmapI(cached, 2, 2)), "DrawCachedBitmapI on another Graphics of the same pixel format returns (0 is Ok)" + Expect.AreEqual &HFFFF0000, second.Pixel(3, 3).argb, "the pixel drawn on another Graphics of the same pixel format" + Expect.Done + End Sub + + [TestCase] + Public Sub Draw_OnGraphicsOfAnotherFormatIsWrongState() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim tile As Bitmap = Bitmap(4!, 4!) + Dim tg As Graphics = Graphics(tile) + tg.Clear Color(255, 255, 0, 0) + Dim first As Bitmap = Bitmap(10!, 10!) + Dim other As Bitmap = Bitmap(10!, 10!, PixelFormat24bppRGB) + Dim g1 As Graphics = Graphics(first) + Dim g2 As Graphics = Graphics(other) + Dim cached As CachedBitmap = CachedBitmap(tile, g1) + Expect.AreEqual 8, CLng(g2.DrawCachedBitmapI(cached, 2, 2)), "DrawCachedBitmapI on a Graphics of a bitmap with another pixel format returns (8 is WrongState)" + Expect.Done + End Sub + +End Module diff --git a/test/package-tests/GdiPlus/Sources/Im_CodecTests.twin b/test/package-tests/GdiPlus/Sources/Im_CodecTests.twin new file mode 100644 index 00000000..0107dd36 --- /dev/null +++ b/test/package-tests/GdiPlus/Sources/Im_CodecTests.twin @@ -0,0 +1,108 @@ +' GdiPlus, ImageCodec and GpImageCodecInfo: the lists of encoders and decoders and the text +' properties of a codec. The lists are read through the declared GDI+ functions, which work. +[TestFixture] +Module Im_CodecTests + + ' Finds the encoder whose FormatDescription is "PNG" in the buffer that GetImageEncoders filled. + Private Function PngEncoder(info As GpImageCodecInfo, buffer() As Byte) As Boolean + Dim count As UINT, size As UINT + GetImageEncodersSize count, size + ReDim buffer(0 To size - 1) + GetImageEncoders count, size, buffer(0) + Dim i As Long + For i = 0 To count - 1 + Im_Copy VarPtr(info), VarPtr(buffer(0)) + i * LenB(Of GpImageCodecInfo), LenB(Of GpImageCodecInfo) + If Replace(info.FormatDescription, vbNullChar, "") = "PNG" Then Return True + Next + Return False + End Function + + [TestCase] + Public Sub GetImageEncoders_ReturnsTheInstalledEncoders() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim encoders() As GpImageCodecInfo = GetImageEncoders() + Dim n As Long = UBound(encoders) - LBound(encoders) + 1 + Expect.IsTrue n >= 5, "the number of elements of the array that GetImageEncoders( ) returns is at least 5 (BMP, JPEG, GIF, TIFF, PNG); it is " & n + Dim found As Boolean + Dim i As Long + For i = LBound(encoders) To UBound(encoders) + Dim one As GpImageCodecInfo = encoders(i) + If one.Clsid.Data1 = ImageCodecPNG().Data1 Then found = True + Next + Expect.IsTrue found, "the encoders that GetImageEncoders( ) returns include the PNG encoder" + Expect.Done + End Sub + + [TestCase] + Public Sub GetImageDecoders_ReturnsTheInstalledDecoders() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim decoders() As GpImageCodecInfo = GetImageDecoders() + Dim n As Long = UBound(decoders) - LBound(decoders) + 1 + Expect.IsTrue n >= 5, "the number of elements of the array that GetImageDecoders( ) returns is at least 5; it is " & n + Dim found As Boolean + Dim i As Long + For i = LBound(decoders) To UBound(decoders) + Dim one As GpImageCodecInfo = decoders(i) + If one.FormatID.Data1 = ImageFormatPNG().Data1 Then found = True + Next + Expect.IsTrue found, "the decoders that GetImageDecoders( ) returns include the PNG decoder" + Expect.Done + End Sub + + [TestCase] + Public Sub GetImageEncodersSize_ListsTheEncodersInABuffer() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim count As UINT, size As UINT + Expect.AreEqual 0, CLng(GetImageEncodersSize(count, size)), "GetImageEncodersSize returns (0 is Ok)" + Expect.IsTrue count >= 5, "the number of encoders is at least 5; it is " & count + Dim buffer() As Byte + ReDim buffer(0 To size - 1) + Expect.AreEqual 0, CLng(GetImageEncoders(count, size, buffer(0))), "GetImageEncoders(count, size, buffer) returns (0 is Ok)" + Dim info As GpImageCodecInfo + Dim found As Boolean + Dim i As Long + For i = 0 To count - 1 + Im_Copy VarPtr(info), VarPtr(buffer(0)) + i * LenB(Of GpImageCodecInfo), LenB(Of GpImageCodecInfo) + If info.Clsid.Data1 = ImageCodecPNG().Data1 Then found = True + Next + Expect.IsTrue found, "the buffer lists the PNG encoder" + Expect.Done + End Sub + + [TestCase] + Public Sub Text_PropertiesHaveNoTerminatingNull() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim info As GpImageCodecInfo + Dim buffer() As Byte + If Not PngEncoder(info, buffer) Then + Expect.Fail "the buffer that GetImageEncoders filled has no encoder with the FormatDescription ""PNG""" + Else + Expect.IsTrue info.FormatDescription = "PNG", "FormatDescription of the PNG encoder equals ""PNG"" (its length is " & Len(info.FormatDescription) & ")" + Expect.IsTrue info.MimeType = "image/png", "MimeType of the PNG encoder equals ""image/png"" (its length is " & Len(info.MimeType) & ")" + Expect.IsTrue info.FilenameExtension = "*.PNG", "FilenameExtension of the PNG encoder equals ""*.PNG"" (its length is " & Len(info.FilenameExtension) & ")" + Expect.IsTrue info.CodecName = "Built-in PNG Codec", "CodecName of the PNG encoder equals ""Built-in PNG Codec"" (its length is " & Len(info.CodecName) & ")" + End If + Expect.Done + End Sub + + [TestCase] + Public Sub Text_PropertiesHoldTheTextBeforeTheNull() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim info As GpImageCodecInfo + Dim buffer() As Byte + If Not PngEncoder(info, buffer) Then + Expect.Fail "the buffer that GetImageEncoders filled has no encoder with the FormatDescription ""PNG""" + Else + Expect.AreEqual "image/png", Replace(info.MimeType, vbNullChar, ""), "MimeType of the PNG encoder without its null characters" + Expect.AreEqual "*.PNG", Replace(info.FilenameExtension, vbNullChar, ""), "FilenameExtension of the PNG encoder without its null characters" + Expect.IsTrue info.IsEncoder, "IsEncoder of the PNG encoder" + End If + Expect.Done + End Sub + +End Module diff --git a/test/package-tests/GdiPlus/Sources/Im_Files.twin b/test/package-tests/GdiPlus/Sources/Im_Files.twin new file mode 100644 index 00000000..e0859150 --- /dev/null +++ b/test/package-tests/GdiPlus/Sources/Im_Files.twin @@ -0,0 +1,124 @@ +' GdiPlus, images: what the cases of Im_* share. Files go only under +' %TEMP%\tbdocs-package-tests\GdiPlus\im, which Im_Clean empties. +Module Im_Files + + Private Declare PtrSafe Function ZFromHBitmap Lib "gdiplus" Alias "GdipCreateBitmapFromHBITMAP" (ByVal hbm As LongPtr, ByVal hpal As LongPtr, nb As LongPtr) As Long + Private Declare PtrSafe Function ZSaveImage Lib "gdiplus" Alias "GdipSaveImageToFile" (ByVal img As LongPtr, ByVal path As LongPtr, clsid As GDIP_UUID, ByVal ep As LongPtr) As Long + Private Declare PtrSafe Function ZDispose Lib "gdiplus" Alias "GdipDisposeImage" (ByVal img As LongPtr) As Long + Private Declare PtrSafe Function ZDeleteObject Lib "gdi32" Alias "DeleteObject" (ByVal h As LongPtr) As Long + Private Declare PtrSafe Function ZCreateEnhMetaFile Lib "gdi32" Alias "CreateEnhMetaFileW" (ByVal hdcRef As LongPtr, ByVal fileName As LongPtr, ByVal rc As LongPtr, ByVal desc As LongPtr) As LongPtr + Private Declare PtrSafe Function ZCloseEnhMetaFile Lib "gdi32" Alias "CloseEnhMetaFile" (ByVal hdc As LongPtr) As LongPtr + Private Declare PtrSafe Function ZRectangle Lib "gdi32" Alias "Rectangle" (ByVal hdc As LongPtr, ByVal l As Long, ByVal t As Long, ByVal r As Long, ByVal b As Long) As Long + Private Declare PtrSafe Function ZCreateSolidBrush Lib "gdi32" Alias "CreateSolidBrush" (ByVal c As Long) As LongPtr + Private Declare PtrSafe Function ZSelectObject Lib "gdi32" Alias "SelectObject" (ByVal hdc As LongPtr, ByVal h As LongPtr) As LongPtr + Private Declare PtrSafe Function ZCreateCompatibleDC Lib "gdi32" Alias "CreateCompatibleDC" (ByVal hdc As LongPtr) As LongPtr + Private Declare PtrSafe Function ZDeleteEnhMetaFile Lib "gdi32" Alias "DeleteEnhMetaFile" (ByVal h As LongPtr) As Long + Private Declare PtrSafe Function ZCreateMetaFile Lib "gdi32" Alias "CreateMetaFileW" (ByVal name As LongPtr) As LongPtr + Private Declare PtrSafe Function ZCloseMetaFile Lib "gdi32" Alias "CloseMetaFile" (ByVal hdc As LongPtr) As LongPtr + Private Declare PtrSafe Function ZDeleteDC Lib "gdi32" Alias "DeleteDC" (ByVal hdc As LongPtr) As Long + + Private Declare PtrSafe Sub ZMoveMemory Lib "kernel32" Alias "RtlMoveMemory" (ByVal dst As LongPtr, ByVal src As LongPtr, ByVal cb As LongPtr) + + Public Sub Im_Copy(ByVal dst As LongPtr, ByVal src As LongPtr, ByVal cb As LongPtr) + ZMoveMemory dst, src, cb + End Sub + + ' The folder of the cases, made when it is not there. + Public Function Im_Dir() As String + Dim p As String = Environ$("TEMP") & "\tbdocs-package-tests" + On Error Resume Next + MkDir p + p = p & "\GdiPlus" + MkDir p + p = p & "\im" + MkDir p + Return p + End Function + + ' Deletes every file of the folder; a file that is still open stays. + Public Sub Im_Clean() + Dim d As String = Im_Dir() + Dim names() As String + ReDim names(0 To 63) + Dim n As Long = 0 + On Error Resume Next + Dim f As String = Dir$(d & "\*.*") + Do While Len(f) > 0 And n < 64 + names(n) = f + n = n + 1 + f = Dir$() + Loop + Dim i As Long + For i = 0 To n - 1 + Kill d & "\" & names(i) + Next + End Sub + + ' The size of a file, or -1 when there is none. + Public Function Im_Len(ByVal path As String) As Long + Dim n As Long = -1 + On Error Resume Next + n = FileLen(path) + If Err.Number <> 0 Then n = -1 + Return n + End Function + + ' A picture with a lot of detail, so that a JPEG of it is larger at a higher quality. + Public Function Im_Noisy(ByVal w As Long, ByVal h As Long) As Bitmap + Dim bmp As Bitmap = New Bitmap(CSng(w), CSng(h)) + Dim x As Long, y As Long + For y = 0 To h - 1 + For x = 0 To w - 1 + bmp.Pixel(x, y) = Color(255, (x * 37 + y * 91) And 255, ((x * 17) Xor (y * 53)) And 255, (x * y + x) And 255) + Next + Next + Return bmp + End Function + + ' Writes a bitmap as a PNG without Image.Save; returns 0 when the file was written. + Public Function Im_SavePng(bmp As Bitmap, ByVal path As String) As Long + Dim hbm As LongPtr, nb As LongPtr + bmp.GetHBitmap Color(255, 255, 255, 255), hbm + Dim st As Long = ZFromHBitmap(hbm, 0, nb) + If st = 0 Then + Dim clsid As GDIP_UUID = ImageCodecPNG() + st = ZSaveImage(nb, StrPtr(path), clsid, 0) + ZDispose nb + End If + ZDeleteObject hbm + Return st + End Function + + ' The handle of an enhanced metafile with one red rectangle; a file name makes GDI write it there. + Public Function Im_RedRectangleEmf(ByVal width As Long, ByVal height As Long, Optional ByVal path As String = "") As LongPtr + Dim dc As LongPtr + If Len(path) > 0 Then dc = ZCreateEnhMetaFile(0, StrPtr(path), 0, 0) Else dc = ZCreateEnhMetaFile(0, 0, 0, 0) + Dim brush As LongPtr = ZCreateSolidBrush(&HFF) + Dim old As LongPtr = ZSelectObject(dc, brush) + ZRectangle dc, 0, 0, width, height + ZSelectObject dc, old + ZDeleteObject brush + Return ZCloseEnhMetaFile(dc) + End Function + + ' The handle of a Windows metafile in memory with one rectangle. + Public Function Im_RectangleWmf(ByVal width As Long, ByVal height As Long) As LongPtr + Dim dc As LongPtr = ZCreateMetaFile(0) + ZRectangle dc, 0, 0, width, height + Return ZCloseMetaFile(dc) + End Function + + Public Sub Im_DeleteEmf(ByVal hemf As LongPtr) + ZDeleteEnhMetaFile hemf + End Sub + + ' A device context to record a metafile for; Im_ReleaseDc gives it back. + Public Function Im_MakeDc() As LongPtr + Return ZCreateCompatibleDC(0) + End Function + + Public Sub Im_ReleaseDc(ByVal dc As LongPtr) + ZDeleteDC dc + End Sub + +End Module diff --git a/test/package-tests/GdiPlus/Sources/Im_ImageTests.twin b/test/package-tests/GdiPlus/Sources/Im_ImageTests.twin new file mode 100644 index 00000000..57df98f7 --- /dev/null +++ b/test/package-tests/GdiPlus/Sources/Im_ImageTests.twin @@ -0,0 +1,281 @@ +' GdiPlus, Image: saving with encoder parameters, the palette, properties, encoder parameter lists +' and cloning. A Bitmap is used wherever a picture is needed, since an Image of a file has no +' pixel access and Bitmap inherits every member tested here. +[TestFixture] +Module Im_ImageTests + + ' ---- Save, SaveAdd, SaveAddImage + + [TestCase] + Public Sub Save_EmptyParameterArrayWritesFile() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim path As String = Im_Dir() & "\save-empty.png" + Dim bmp As Bitmap = Im_Noisy(16, 16) + Dim ep(0 To 0) As GpEncoderParameter ' element 0 is the dummy: no parameters + Dim st As GpStatus = bmp.Save(path, ImageCodecPNG(), ep) + Expect.AreEqual 0, CLng(st), "Save of a PNG with the one-element parameter array (no parameters) returns (0 is Ok)" + Expect.IsTrue Im_Len(path) > 0, "Save of a PNG with the one-element parameter array writes a file of more than 0 bytes (its length is " & Im_Len(path) & ")" + Set bmp = Nothing + Im_Clean + Expect.Done + End Sub + + [TestCase] + Public Sub Save_JpegQualityChangesFileSize() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim low As String = Im_Dir() & "\quality-10.jpg" + Dim high As String = Im_Dir() & "\quality-100.jpg" + Dim bmp As Bitmap = Im_Noisy(64, 64) + Dim quality As Long + Dim ep(0 To 1) As GpEncoderParameter + ep(1).Guid = EncoderQuality() + ep(1)._count = 1 + ep(1)._type = EncoderParameterValueTypeLong + ep(1)._address = VarPtr(quality) + quality = 10 + Dim st1 As GpStatus = bmp.Save(low, ImageCodecJPG(), ep) + quality = 100 + Dim st2 As GpStatus = bmp.Save(high, ImageCodecJPG(), ep) + Expect.AreEqual 0, CLng(st1), "Save of a JPEG with a quality of 10 returns (0 is Ok)" + Expect.AreEqual 0, CLng(st2), "Save of a JPEG with a quality of 100 returns (0 is Ok)" + Dim sizeLow As Long = Im_Len(low) + Dim sizeHigh As Long = Im_Len(high) + Expect.IsTrue sizeLow > 0 And sizeHigh > 0, "Save of a JPEG writes a file (sizes " & sizeLow & " and " & sizeHigh & ")" + Expect.IsTrue sizeLow < sizeHigh, "a JPEG saved with a quality of 10 is smaller than one saved with 100 (sizes " & sizeLow & " and " & sizeHigh & ")" + Set bmp = Nothing + Im_Clean + Expect.Done + End Sub + + [TestCase] + Public Sub Save_ParameterArrayStartingAtOneIsRefused() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim path As String = Im_Dir() & "\save-lbound1.png" + Dim bmp As Bitmap = Im_Noisy(8, 8) + Dim ep(1 To 2) As GpEncoderParameter + Dim st As GpStatus = bmp.Save(path, ImageCodecPNG(), ep) + Expect.AreEqual 2, CLng(st), "Save with a parameter array whose lower bound is 1 returns (2 is InvalidParameter)" + Set bmp = Nothing + Im_Clean + Expect.Done + End Sub + + [TestCase] + Public Sub SaveAdd_MultiFrameTiffHasTwoFrames() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim path As String = Im_Dir() & "\multi.tif" + Dim first As Bitmap = Im_Noisy(16, 16) + Dim second As Bitmap = Im_Noisy(16, 16) + Dim flag As Long + Dim ep(0 To 1) As GpEncoderParameter + ep(1).Guid = EncoderSaveFlag() + ep(1)._count = 1 + ep(1)._type = EncoderParameterValueTypeLong + ep(1)._address = VarPtr(flag) + flag = EncoderValueMultiFrame + Dim st1 As GpStatus = first.Save(path, ImageCodecTIF(), ep) + flag = EncoderValueFrameDimensionPage + Dim st2 As GpStatus = first.SaveAddImage(second, ep) + flag = EncoderValueFlush + Dim st3 As GpStatus = first.SaveAdd(ep) + Expect.AreEqual 0, CLng(st1), "Save of the first frame of a multi-frame TIFF returns (0 is Ok)" + Expect.AreEqual 0, CLng(st2), "SaveAddImage of the second frame returns (0 is Ok)" + Expect.AreEqual 0, CLng(st3), "SaveAdd that flushes the file returns (0 is Ok)" + Set first = Nothing + Set second = Nothing + Dim loaded As GdiPlus.Image = GdiPlus.Image(path) + Expect.AreEqual 2, CLng(loaded.FrameCount(FrameDimensionPage())), "the page count of the TIFF written with Save, SaveAddImage and SaveAdd" + Set loaded = Nothing + Im_Clean + Expect.Done + End Sub + + ' ---- Palette + + [TestCase] + Public Sub Palette_LetChangesThePicture() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = New Bitmap(4!, 4!, PixelFormat8bppIndexed) + Dim before As Long = bmp.Pixel(0, 0).argb + Dim pal(1 To 1) As ColorPalette + pal(1).flags = 0 + pal(1).count = 256 + Dim i As Long + For i = 0 To 255 + pal(1).entries(i) = &HFF112233 + Next + bmp.Palette = pal + Expect.AreEqual 0, CLng(bmp.LastResult), "setting Palette of an 8-bit indexed bitmap leaves LastResult (0 is Ok)" + Expect.AreEqual &HFF112233, bmp.Pixel(0, 0).argb, "the colour of a pixel of an 8-bit indexed bitmap after Palette is set to one colour (it was " & Hex$(before) & ")" + Expect.Done + End Sub + + ' ---- Properties + + [TestCase] + Public Sub PropertyItem_GetReturnsTheItem() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = New Bitmap(4!, 4!) + Dim text() As Byte = StrConv("A test picture" & vbNullChar, vbFromUnicode) + Dim item As GpPropertyItem + item.Id = PropertyTagImageDescription + item._type = PropertyTagTypeASCII + item._length = UBound(text) + 1 + item._address = VarPtr(text(0)) + bmp.PropertyItem = item + Expect.AreEqual 1, CLng(bmp.PropertyCount), "PropertyCount after PropertyItem is set" + Dim items() As GpPropertyItem = bmp.PropertyItem(PropertyTagImageDescription) + Expect.AreEqual 1, UBound(items) - LBound(items) + 1, "the number of elements of the array that PropertyItem returns" + Dim got As GpPropertyItem = items(1) + Expect.AreEqual PropertyTagImageDescription, got.Id, "the Id of the item that PropertyItem returns" + Expect.AreEqual "A test picture", Replace(got.TheASCII, vbNullChar, ""), "the text of the item that PropertyItem returns, without its null characters" + Expect.Done + End Sub + + [TestCase] + Public Sub PropertyItem_PropertyOfTheReturnedElement() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = New Bitmap(4!, 4!) + Dim text() As Byte = StrConv("A test picture" & vbNullChar, vbFromUnicode) + Dim item As GpPropertyItem + item.Id = PropertyTagImageDescription + item._type = PropertyTagTypeASCII + item._length = UBound(text) + 1 + item._address = VarPtr(text(0)) + bmp.PropertyItem = item + Dim items() As GpPropertyItem = bmp.PropertyItem(PropertyTagImageDescription) + Expect.AreEqual PropertyTagImageDescription, items(1).Id, "the Id field of items(1) of the array that PropertyItem returns" + Expect.AreEqual "A test picture", Replace(items(1).TheASCII, vbNullChar, ""), "the TheASCII property of items(1) of the array that PropertyItem returns" + Expect.Done + End Sub + + [TestCase] + Public Sub GetAllPropertyItems_ReturnsEveryItem() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = New Bitmap(4!, 4!) + Dim text() As Byte = StrConv("First" & vbNullChar, vbFromUnicode) + Dim ratio(0 To 1) As Long + ratio(0) = 7 + ratio(1) = 2 + Dim item As GpPropertyItem + item.Id = PropertyTagImageDescription + item._type = PropertyTagTypeASCII + item._length = UBound(text) + 1 + item._address = VarPtr(text(0)) + bmp.PropertyItem = item + item.Id = PropertyTagXResolution + item._type = PropertyTagTypeRational + item._length = 8 + item._address = VarPtr(ratio(0)) + bmp.PropertyItem = item + Dim items() As GpPropertyItem = bmp.GetAllPropertyItems() + Expect.AreEqual 2, UBound(items) - LBound(items) + 1, "the number of elements of the array that GetAllPropertyItems returns" + Dim a As GpPropertyItem = items(LBound(items)) + Dim b As GpPropertyItem = items(UBound(items)) + Expect.IsTrue (a.Id = PropertyTagImageDescription And b.Id = PropertyTagXResolution) Or (a.Id = PropertyTagXResolution And b.Id = PropertyTagImageDescription), "the Ids of the two items that GetAllPropertyItems returns (" & Hex$(a.Id) & " and " & Hex$(b.Id) & ")" + Expect.Done + End Sub + + [TestCase] + Public Sub PropertyItem_RemoveTakesTheItemAway() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = New Bitmap(4!, 4!) + Dim text() As Byte = StrConv("Gone" & vbNullChar, vbFromUnicode) + Dim item As GpPropertyItem + item.Id = PropertyTagImageDescription + item._type = PropertyTagTypeASCII + item._length = UBound(text) + 1 + item._address = VarPtr(text(0)) + bmp.PropertyItem = item + Expect.AreEqual 0, CLng(bmp.RemovePropertyItem(PropertyTagImageDescription)), "RemovePropertyItem returns (0 is Ok)" + Expect.AreEqual 0, CLng(bmp.PropertyCount), "PropertyCount after RemovePropertyItem" + Expect.Done + End Sub + + ' ---- Encoder parameter list + + [TestCase] + Public Sub GetEncoderParameterList_JpegListsTheQuality() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = New Bitmap(4!, 4!) + Dim ps() As GpEncoderParameter = bmp.GetEncoderParameterList(ImageCodecJPG()) + Expect.AreEqual 0, CLng(bmp.LastResult), "GetEncoderParameterList for the JPEG encoder leaves LastResult (0 is Ok)" + Dim found As Boolean + Dim i As Long + For i = LBound(ps) To UBound(ps) + Dim one As GpEncoderParameter = ps(i) + If one.Guid.Data1 = EncoderQuality().Data1 Then found = True + Next + Expect.IsTrue found, "the parameters that GetEncoderParameterList returns for the JPEG encoder include EncoderQuality" + Expect.Done + End Sub + + ' ---- The VB class of the same name + + [TestCase] + Public Sub BareImage_FunctionWorksUnqualified() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim img As GdiPlus.Image = Image(Im_Dir() & "\no-such-file.png") + Expect.AreEqual "Image", TypeName(img), "TypeName of the result of the function Image(filename) written without GdiPlus." + Expect.AreEqual 3, CLng(img.LastResult), "LastResult of Image(filename) of a missing file (3 is OutOfMemory)" + Expect.Done + End Sub + + [TestCase] + Public Sub BareImage_VariableCannotHoldABitmap() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = New Bitmap(4!, 4!) + Dim vbImage As Image + On Error Resume Next + Set vbImage = bmp + Dim errNumber As Long = Err.Number + On Error GoTo 0 + Expect.AreEqual &H80004002, errNumber, "Err.Number of assigning a Bitmap to a variable declared As Image, which is the VB class" + Expect.Done + End Sub + + ' ---- Clone + + [TestCase] + Public Sub Clone_OfABitmapIsAnImage() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = New Bitmap(4!, 3!) + Dim img As GdiPlus.Image = bmp + Dim copy As GdiPlus.Image = img.Clone() + Expect.AreEqual 0, CLng(img.LastResult), "Clone of an Image that holds a Bitmap leaves LastResult (0 is Ok)" + Expect.AreEqual "Image", TypeName(copy), "TypeName of Image.Clone of a Bitmap" + Expect.AreEqual 4, CLng(copy.Width), "Width of Image.Clone of a Bitmap" + Expect.AreEqual 3, CLng(copy.Height), "Height of Image.Clone of a Bitmap" + Expect.Done + End Sub + + [TestCase] + Public Sub Clone_WholeBitmapWithItsRectangle() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = New Bitmap(4!, 3!) + bmp.Pixel(2, 1) = Color(255, 1, 2, 3) + Dim whole As Bitmap = bmp.Clone(GpRect(0, 0, 4, 3), PixelFormat32bppARGB) + Expect.AreEqual 0, CLng(bmp.LastResult), "Bitmap.Clone with the full rectangle leaves LastResult (0 is Ok)" + Expect.AreEqual 4, CLng(whole.Width), "Width of Bitmap.Clone with the full rectangle" + Expect.AreEqual 3, CLng(whole.Height), "Height of Bitmap.Clone with the full rectangle" + Expect.AreEqual &HFF010203, whole.Pixel(2, 1).argb, "the pixel of Bitmap.Clone with the full rectangle" + bmp.Pixel(2, 1) = Color(255, 9, 9, 9) + Expect.AreEqual &HFF010203, whole.Pixel(2, 1).argb, "the pixel of the copy after the original is changed" + Expect.Done + End Sub + +End Module diff --git a/test/package-tests/GdiPlus/Sources/Im_MetafileTests.twin b/test/package-tests/GdiPlus/Sources/Im_MetafileTests.twin new file mode 100644 index 00000000..dc456794 --- /dev/null +++ b/test/package-tests/GdiPlus/Sources/Im_MetafileTests.twin @@ -0,0 +1,167 @@ +' GdiPlus, Metafile: the constructors that take a file name or a reference device context, and +' ConvertToEmfPlus. A GDI call makes the metafiles that are played back. +[TestFixture] +Module Im_MetafileTests + + [TestCase] + Public Sub New_FromEmfHandlePlaysBack() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim mf As Metafile = Metafile(Im_RedRectangleEmf(40, 30), True) + Expect.AreEqual 0, CLng(mf.LastResult), "LastResult of Metafile(hEmf, True) (0 is Ok)" + Expect.AreEqual 2, CLng(mf.Type), "Type of Metafile(hEmf, True) (2 is ImageTypeMetafile)" + Expect.AreEqual 40, CLng(mf.Width), "Width of Metafile(hEmf, True)" + Dim bmp As Bitmap = Bitmap(60!, 50!) + Dim g As Graphics = Graphics(bmp) + g.Clear Color(255, 255, 255, 255) + g.DrawImage mf, 5, 5 + Expect.AreEqual &HFFFF0000, bmp.Pixel(20, 20).argb, "the pixel inside the rectangle that the metafile draws" + Expect.Done + End Sub + + [TestCase] + Public Sub New_FromWmfHandleWithAPlaceableHeader() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim h As WmfPlaceableFileHeader + h.Key = &H9AC6CDD7 + h.BoundingBox.Right = 40 + h.BoundingBox.Bottom = 30 + h.Inch = 1440 + Dim cs As Long = &HCDD7& Xor &H9AC6& Xor 40 Xor 30 Xor 1440 + If cs > 32767 Then cs = cs - 65536 + h.Checksum = cs + Dim mf As Metafile = Metafile(Im_RectangleWmf(40, 30), h, True) + Expect.AreEqual 0, CLng(mf.LastResult), "LastResult of Metafile(hWmf, header, True) (0 is Ok)" + Expect.AreEqual 2, CLng(mf.Type), "Type of Metafile(hWmf, header, True) (2 is ImageTypeMetafile)" + Expect.IsTrue mf.MetafileHeader.IsWmf, "the metafile that Metafile(hWmf, header, True) makes is a WMF" + Expect.Done + End Sub + + [TestCase] + Public Sub New_FromFileName() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim path As String = Im_Dir() & "\red.emf" + Im_DeleteEmf Im_RedRectangleEmf(40, 30, path) + Expect.IsTrue Im_Len(path) > 0, "the metafile file that GDI wrote exists" + On Error Resume Next + Dim mf As Metafile = Metafile(path) + Dim errNumber As Long = Err.Number + Dim errText As String = Err.Description + On Error GoTo 0 + Expect.AreEqual 0, errNumber, "Err.Number of Metafile(filename) (" & errText & ")" + If errNumber = 0 Then + Expect.AreEqual 0, CLng(mf.LastResult), "LastResult of Metafile(filename) (0 is Ok)" + Expect.AreEqual 2, CLng(mf.Type), "Type of Metafile(filename) (2 is ImageTypeMetafile)" + Expect.AreEqual 40, CLng(mf.Width), "Width of Metafile(filename)" + End If + Set mf = Nothing + Im_Clean + Expect.Done + End Sub + + [TestCase] + Public Sub New_RecordingToFileNameWithReferenceDc() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim path As String = Im_Dir() & "\recorded.emf" + Dim dc As LongPtr = Im_MakeDc() + On Error Resume Next + Dim mf As Metafile = Metafile(path, dc) + Dim errNumber As Long = Err.Number + Dim errText As String = Err.Description + On Error GoTo 0 + Expect.AreEqual 0, errNumber, "Err.Number of Metafile(filename, referenceHdc) (" & errText & ")" + If errNumber = 0 Then + Expect.AreEqual 0, CLng(mf.LastResult), "LastResult of Metafile(filename, referenceHdc) (0 is Ok)" + Expect.AreEqual 2, CLng(mf.Type), "Type of Metafile(filename, referenceHdc) (2 is ImageTypeMetafile)" + End If + Set mf = Nothing + Im_ReleaseDc dc + Im_Clean + Expect.Done + End Sub + + [TestCase] + Public Sub New_RecordingWithReferenceDcAndType() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim dc As LongPtr = Im_MakeDc() + On Error Resume Next + Dim mf As Metafile = Metafile(dc, EmfTypeEmfPlusOnly) + Dim errNumber As Long = Err.Number + Dim errText As String = Err.Description + On Error GoTo 0 + Expect.AreEqual 0, errNumber, "Err.Number of Metafile(referenceHdc, EmfTypeEmfPlusOnly) (" & errText & ")" + If errNumber = 0 Then + Expect.AreEqual 0, CLng(mf.LastResult), "LastResult of Metafile(referenceHdc, EmfTypeEmfPlusOnly) (0 is Ok)" + Expect.AreEqual 2, CLng(mf.Type), "Type of Metafile(referenceHdc, EmfTypeEmfPlusOnly) (2 is ImageTypeMetafile)" + End If + Set mf = Nothing + Im_ReleaseDc dc + Expect.Done + End Sub + + [TestCase] + Public Sub New_ReferenceDcAloneRecords() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim dc As LongPtr = Im_MakeDc() + Dim mf As Metafile = Metafile(dc) + Expect.AreEqual 0, CLng(mf.LastResult), "LastResult of Metafile(referenceHdc) (0 is Ok, 1 is GenericError)" + Set mf = Nothing + Im_ReleaseDc dc + Expect.Done + End Sub + + [TestCase] + Public Sub ConvertToEmfPlus_WithFileName() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim path As String = Im_Dir() & "\converted.emf" + Dim mf As Metafile = Metafile(Im_RedRectangleEmf(40, 30), True) + Dim bmp As Bitmap = Bitmap(10!, 10!) + Dim g As Graphics = Graphics(bmp) + Expect.AreEqual 0, CLng(mf.ConvertToEmfPlus(g, path)), "ConvertToEmfPlus with a file name returns (0 is Ok)" + Expect.IsTrue mf.MetafileHeader.IsEmfPlus, "the metafile is EMF+ after ConvertToEmfPlus with a file name" + Set mf = Nothing + Im_Clean + Expect.Done + End Sub + + [TestCase] + Public Sub ConvertToEmfPlus_WithoutFileNameOrStream() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim mf As Metafile = Metafile(Im_RedRectangleEmf(40, 30), True) + Dim bmp As Bitmap = Bitmap(10!, 10!) + Dim g As Graphics = Graphics(bmp) + On Error Resume Next + Dim st As GpStatus = mf.ConvertToEmfPlus(g) + Dim errNumber As Long = Err.Number + Dim errText As String = Err.Description + On Error GoTo 0 + Expect.AreEqual 0, errNumber, "Err.Number of ConvertToEmfPlus(g) (" & errText & ")" + If errNumber = 0 Then + Expect.AreEqual 0, CLng(st), "ConvertToEmfPlus(g) returns (0 is Ok)" + Expect.IsTrue mf.MetafileHeader.IsEmfPlus, "the metafile is EMF+ after ConvertToEmfPlus(g)" + End If + Expect.Done + End Sub + + [TestCase] + Public Sub Image_LoadsAMetafileFile() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim path As String = Im_Dir() & "\viaimage.emf" + Im_DeleteEmf Im_RedRectangleEmf(40, 30, path) + Dim img As GdiPlus.Image = GdiPlus.Image(path) + Expect.AreEqual 0, CLng(img.LastResult), "LastResult of GdiPlus.Image(filename) of a metafile file (0 is Ok)" + Expect.AreEqual 2, CLng(img.Type), "Type of GdiPlus.Image(filename) of a metafile file (2 is ImageTypeMetafile)" + Set img = Nothing + Im_Clean + Expect.Done + End Sub + +End Module diff --git a/test/package-tests/GdiPlus/Sources/Im_TypeTests.twin b/test/package-tests/GdiPlus/Sources/Im_TypeTests.twin new file mode 100644 index 00000000..e5d5422f --- /dev/null +++ b/test/package-tests/GdiPlus/Sources/Im_TypeTests.twin @@ -0,0 +1,202 @@ +' GdiPlus, the imaging types: GpPropertyItem readers, ColorPalette and ColorMask. A property item +' is described by hand: its type, its length and the address of a variable that holds the value. +[TestFixture] +Module Im_TypeTests + + [TestCase] + Public Sub GpPropertyItem_TheSByteReadsASignedByte() + Expect.Begin + Dim v As Byte = 5 + Dim item As GpPropertyItem + item.Id = PropertyTagImageDescription + item._type = PropertyTagTypeSByte + item._length = 1 + item._address = VarPtr(v) + Expect.IsTrue item.IsSByte, "IsSByte of an item of the type signed byte" + Expect.AreEqual 5, item.TheSByte, "TheSByte of an item of the type signed byte that holds 5" + Expect.Done + End Sub + + [TestCase] + Public Sub GpPropertyItem_TheSShortReadsASignedShort() + Expect.Begin + Dim v As Integer = -300 + Dim item As GpPropertyItem + item.Id = PropertyTagImageDescription + item._type = PropertyTagTypeSShort + item._length = 2 + item._address = VarPtr(v) + Expect.IsTrue item.IsSShort, "IsSShort of an item of the type signed short" + Expect.AreEqual -300, item.TheSShort, "TheSShort of an item of the type signed short that holds -300" + Expect.Done + End Sub + + [TestCase] + Public Sub GpPropertyItem_TheSLongReadsASignedLong() + Expect.Begin + Dim v As Long = -100000 + Dim item As GpPropertyItem + item.Id = PropertyTagImageDescription + item._type = PropertyTagTypeSLong + item._length = 4 + item._address = VarPtr(v) + Expect.IsTrue item.IsSLong, "IsSLong of an item of the type signed long" + Expect.AreEqual -100000, item.TheSLong, "TheSLong of an item of the type signed long that holds -100000" + Expect.Done + End Sub + + [TestCase] + Public Sub GpPropertyItem_TheDoubleReadsADouble() + Expect.Begin + Dim v As Double = 2.5 + Dim item As GpPropertyItem + item.Id = PropertyTagImageDescription + item._type = PropertyTagTypeDouble + item._length = 8 + item._address = VarPtr(v) + Expect.IsTrue item.IsDouble, "IsDouble of an item of the type double" + Expect.AreEqual 2.5, item.TheDouble, "TheDouble of an item of the type double that holds 2.5" + Expect.Done + End Sub + + [TestCase] + Public Sub GpPropertyItem_AsDoubleCoversTheSignedAndDoubleTypes() + Expect.Begin + Dim sb As Byte = 5 + Dim ss As Integer = -300 + Dim sl As Long = -100000 + Dim d As Double = 2.5 + Dim item As GpPropertyItem + item.Id = PropertyTagImageDescription + + item._type = PropertyTagTypeSByte + item._length = 1 + item._address = VarPtr(sb) + Expect.AreEqual 5, item.AsDouble, "AsDouble of an item of the type signed byte that holds 5" + + item._type = PropertyTagTypeSShort + item._length = 2 + item._address = VarPtr(ss) + Expect.AreEqual -300, item.AsDouble, "AsDouble of an item of the type signed short that holds -300" + + item._type = PropertyTagTypeSLong + item._length = 4 + item._address = VarPtr(sl) + Expect.AreEqual -100000, item.AsDouble, "AsDouble of an item of the type signed long that holds -100000" + + item._type = PropertyTagTypeDouble + item._length = 8 + item._address = VarPtr(d) + Expect.AreEqual 2.5, item.AsDouble, "AsDouble of an item of the type double that holds 2.5" + Expect.Done + End Sub + + [TestCase] + Public Sub GpPropertyItem_TheSRationalReadsASignedRational() + Expect.Begin + Dim ratio(0 To 1) As Long + ratio(0) = -7 + ratio(1) = 2 + Dim item As GpPropertyItem + item.Id = PropertyTagXResolution + item._type = PropertyTagTypeSRational + item._length = 8 + item._address = VarPtr(ratio(0)) + Expect.AreEqual -3.5, item.TheSRational, "TheSRational of an item that holds -7/2" + Expect.Done + End Sub + + [TestCase] + Public Sub GpPropertyItem_GetSRationalReadsASignedRational() + Expect.Begin + Dim ratio(0 To 1) As Long + ratio(0) = -7 + ratio(1) = 2 + Dim item As GpPropertyItem + item.Id = PropertyTagXResolution + item._type = PropertyTagTypeSRational + item._length = 8 + item._address = VarPtr(ratio(0)) + Dim num As Long, denom As Long + item.GetSRational num, denom + Expect.AreEqual -7, num, "the numerator that GetSRational reads from an item that holds -7/2" + Expect.AreEqual 2, denom, "the denominator that GetSRational reads from an item that holds -7/2" + Expect.Done + End Sub + + [TestCase] + Public Sub GpPropertyItem_UnsignedReadersWork() + Expect.Begin + Dim b As Byte = 200 + Dim s As Integer = -1 ' 65535 as an unsigned short + Dim ratio(0 To 1) As Long + ratio(0) = 7 + ratio(1) = 2 + Dim item As GpPropertyItem + item.Id = PropertyTagImageDescription + + item._type = PropertyTagTypeByte + item._length = 1 + item._address = VarPtr(b) + Expect.AreEqual 200, item.TheByte, "TheByte of an item of the type byte that holds 200" + + item._type = PropertyTagTypeShort + item._length = 2 + item._address = VarPtr(s) + Expect.AreEqual 65535, item.TheShort, "TheShort of an item of the type short that holds 65535" + + item._type = PropertyTagTypeRational + item._length = 8 + item._address = VarPtr(ratio(0)) + Expect.AreEqual 3.5, item.TheRational, "TheRational of an item that holds 7/2" + Expect.Done + End Sub + + ' ---- ColorPalette + + [TestCase] + Public Sub ColorPalette_FunctionCopiesTheColours() + Expect.Begin + Dim colours(0 To 1) As Long + colours(0) = &HFF000000 + colours(1) = &HFFFFFFFF + Dim made As ColorPalette = ColorPalette(PaletteFlagsGrayScale, colours) + Expect.AreEqual 2, made.count, "count of ColorPalette(PaletteFlagsGrayScale, colours) for two colours" + Expect.AreEqual CLng(PaletteFlagsGrayScale), CLng(made.flags), "flags of ColorPalette(PaletteFlagsGrayScale, colours)" + Expect.AreEqual &HFF000000, made.entries(0), "entries(0) of ColorPalette(PaletteFlagsGrayScale, colours)" + Expect.AreEqual &HFFFFFFFF, made.entries(1), "entries(1) of ColorPalette(PaletteFlagsGrayScale, colours)" + Expect.Done + End Sub + + [TestCase] + Public Sub ColorPalette_EntriesSetOneAtATimeAreKept() + Expect.Begin + Dim pal As ColorPalette + pal.flags = PaletteFlagsGrayScale + pal.count = 2 + pal.entries(0) = &HFF000000 + pal.entries(1) = &HFFFFFFFF + Expect.AreEqual 2, pal.count, "count of a palette filled in by hand" + Expect.AreEqual &HFFFFFFFF, pal.entries(1), "entries(1) of a palette filled in by hand" + Expect.Done + End Sub + + ' ---- ColorMask + + [TestCase] + Public Sub ColorMask_AlphaShiftOfTheMaskedValueIsMinusOne() + Expect.Begin + Dim argb As Long = CColor.Orange + Expect.AreEqual &HFF000000, AlphaMask, "AlphaMask" + Expect.AreEqual &HFF0000, RedMask, "RedMask" + Expect.AreEqual &HFF00&, GreenMask, "GreenMask" + Expect.AreEqual &HFF&, BlueMask, "BlueMask" + Expect.AreEqual 255, (argb And RedMask) >> RedShift, "the red of CColor.Orange through RedMask and RedShift" + Expect.AreEqual 165, (argb And GreenMask) >> GreenShift, "the green of CColor.Orange through GreenMask and GreenShift" + Expect.AreEqual -1, (argb And AlphaMask) >> AlphaShift, "the alpha of CColor.Orange through AlphaMask and AlphaShift (the sign bit is copied)" + Expect.AreEqual 255, ((argb And AlphaMask) >> AlphaShift) And &HFF, "the alpha of CColor.Orange through AlphaMask, AlphaShift and &HFF" + Expect.AreEqual 255, Color(argb).Alpha, "Alpha of Color(CColor.Orange)" + Expect.Done + End Sub + +End Module diff --git a/test/package-tests/GdiPlus/Sources/Im_ZStackTests.twin b/test/package-tests/GdiPlus/Sources/Im_ZStackTests.twin new file mode 100644 index 00000000..aab276c0 --- /dev/null +++ b/test/package-tests/GdiPlus/Sources/Im_ZStackTests.twin @@ -0,0 +1,19 @@ +' GdiPlus, Image.Palette: reading it overflows the stack in a build of this package. After a stack +' overflow the cases that follow have been seen not to return, so this fixture is the last file. +[TestFixture] +Module Im_ZStackTests + + [TestCase] + Public Sub Palette_GetReturnsThePalette() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim bmp As Bitmap = New Bitmap(4!, 4!, PixelFormat8bppIndexed) + Dim pal() As ColorPalette = bmp.Palette + Expect.AreEqual 0, CLng(bmp.LastResult), "Palette of an 8-bit indexed bitmap leaves LastResult (0 is Ok)" + Expect.AreEqual 1, UBound(pal) - LBound(pal) + 1, "the number of elements of the array that Palette of an 8-bit indexed bitmap returns" + Dim one As ColorPalette = pal(LBound(pal)) + Expect.AreEqual 256, one.count, "the count of the palette of an 8-bit indexed bitmap" + Expect.Done + End Sub + +End Module diff --git a/test/package-tests/GdiPlus/Sources/RunAll.twin b/test/package-tests/GdiPlus/Sources/RunAll.twin new file mode 100644 index 00000000..3297df54 --- /dev/null +++ b/test/package-tests/GdiPlus/Sources/RunAll.twin @@ -0,0 +1,2186 @@ +' Generated by scripts/package_tests.mjs from this project's [TestCase] Subs and expect.json: +' do not edit. Run Sub Main: each case prints PASS, FAIL or SKIP in the DEBUG CONSOLE. +' The cases are written for the package GdiPlus, version 0.9.45.0. Each one checks what is +' correct, so a case that fails names a defect of the package. +Module RunAll + + Public Sub Main() + Expect.StartRun "GdiPlus 0.9.45.0: 554 test cases" + On Error Resume Next + Expect.StartCase + Err.Clear + Br_BrushColourTests.SolidBrush_VBColour_PaintsThatColour + Expect.EndCase "Br_BrushColourTests.SolidBrush_VBColour_PaintsThatColour", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_BrushColourTests.SolidBrush_SystemColour_IsTranslated + Expect.EndCase "Br_BrushColourTests.SolidBrush_SystemColour_IsTranslated", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_BrushColourTests.SolidBrush_RGBForm_IsOpaque + Expect.EndCase "Br_BrushColourTests.SolidBrush_RGBForm_IsOpaque", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_BrushColourTests.SolidBrush_CColorTransparent_PaintsNothing + Expect.EndCase "Br_BrushColourTests.SolidBrush_CColorTransparent_PaintsNothing", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_BrushColourTests.SolidBrush_RGBProperty_Let_TakesAVBColour + Expect.EndCase "Br_BrushColourTests.SolidBrush_RGBProperty_Let_TakesAVBColour", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_BrushColourTests.SolidBrush_RGBProperty_Get_ReturnsAVBColour + Expect.EndCase "Br_BrushColourTests.SolidBrush_RGBProperty_Get_ReturnsAVBColour", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_BrushColourTests.HatchBrush_RGBForm_VBColour_UsesThatColour + Expect.EndCase "Br_BrushColourTests.HatchBrush_RGBForm_VBColour_UsesThatColour", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_BrushColourTests.LinearGradientBrush_RGBForm_VBColour_UsesThatColour + Expect.EndCase "Br_BrushColourTests.LinearGradientBrush_RGBForm_VBColour_UsesThatColour", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_BrushColourTests.LinearGradientBrush_RGBForm_Points_VBColour_UsesThatColour + Expect.EndCase "Br_BrushColourTests.LinearGradientBrush_RGBForm_Points_VBColour_UsesThatColour", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_BrushColourTests.LinearGradientBrush_SetLinearColors_RGBForm_VBColour_UsesThatColour + Expect.EndCase "Br_BrushColourTests.LinearGradientBrush_SetLinearColors_RGBForm_VBColour_UsesThatColour", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_BrushColourTests.PathGradientBrush_CenterRGB_VBColour_UsesThatColour + Expect.EndCase "Br_BrushColourTests.PathGradientBrush_CenterRGB_VBColour_UsesThatColour", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_BrushColourTests.Pen_RGBForm_VBColour_UsesThatColour + Expect.EndCase "Br_BrushColourTests.Pen_RGBForm_VBColour_UsesThatColour", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_BrushColourTests.Pen_RGBForm_VBColour_DrawsThatColour + Expect.EndCase "Br_BrushColourTests.Pen_RGBForm_VBColour_DrawsThatColour", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_BrushColourTests.Pen_RGBProperty_Let_TakesAVBColour + Expect.EndCase "Br_BrushColourTests.Pen_RGBProperty_Let_TakesAVBColour", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_BrushColourTests.Pen_RGBProperty_Get_ReturnsAVBColour + Expect.EndCase "Br_BrushColourTests.Pen_RGBProperty_Get_ReturnsAVBColour", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_BrushColourTests.Pen_Set_RGBForm_VBColour_UsesThatColour + Expect.EndCase "Br_BrushColourTests.Pen_Set_RGBForm_VBColour_UsesThatColour", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_BrushTests.LastResult_StaysUntilClearResult + Expect.EndCase "Br_BrushTests.LastResult_StaysUntilClearResult", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_BrushTests.Clone_AfterEarlierFailure_ReturnsTheCopy + Expect.EndCase "Br_BrushTests.Clone_AfterEarlierFailure_ReturnsTheCopy", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_BrushTests.Clone_AfterClearResult_ReturnsTheCopy + Expect.EndCase "Br_BrushTests.Clone_AfterClearResult_ReturnsTheCopy", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_BrushTests.TextureBrush_Image_AfterEarlierFailure_ReturnsTheImage + Expect.EndCase "Br_BrushTests.TextureBrush_Image_AfterEarlierFailure_ReturnsTheImage", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_BrushTests.Type_PathGradientBrush_ReturnsPathGradient + Expect.EndCase "Br_BrushTests.Type_PathGradientBrush_ReturnsPathGradient", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_BrushTests.Type_EachBrushClass_ReturnsItsKind + Expect.EndCase "Br_BrushTests.Type_EachBrushClass_ReturnsItsKind", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_BrushTests.CloneBrush_KeepsTheClassAndTheSettings + Expect.EndCase "Br_BrushTests.CloneBrush_KeepsTheClassAndTheSettings", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_BrushTests.SolidBrush_Clone_IsIndependent + Expect.EndCase "Br_BrushTests.SolidBrush_Clone_IsIndependent", Err.Number, Err.Description + Expect.Skip "Br_BrushTests.HatchBrush_Function_ColorForm_Returns", "it ends in a native exception in version 0.9.45.0" + Expect.Skip "Br_BrushTests.HatchBrush_Function_RGBForm_Returns", "it ends in a native exception in version 0.9.45.0" + Expect.StartCase + Err.Clear + Br_BrushTests.HatchBrush_New_PaintsTheTile + Expect.EndCase "Br_BrushTests.HatchBrush_New_PaintsTheTile", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_BrushTests.TextureBrushI_UsesTheLongRectangle + Expect.EndCase "Br_BrushTests.TextureBrushI_UsesTheLongRectangle", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_BrushTests.TextureBrush_NewWithSixNumbers_IsTheSingleForm + Expect.EndCase "Br_BrushTests.TextureBrush_NewWithSixNumbers_IsTheSingleForm", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_BrushTests.TextureBrush_Tile_RepeatsFromTheOrigin + Expect.EndCase "Br_BrushTests.TextureBrush_Tile_RepeatsFromTheOrigin", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_BrushTests.TextureBrush_Rectangle_UsesThePartOfTheImage + Expect.EndCase "Br_BrushTests.TextureBrush_Rectangle_UsesThePartOfTheImage", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_BrushTests.TextureBrush_WrapMode_FlipXMirrorsAndClampDrawsOnce + Expect.EndCase "Br_BrushTests.TextureBrush_WrapMode_FlipXMirrorsAndClampDrawsOnce", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_BrushTests.TextureBrush_TranslateTransform_MovesTheTiles + Expect.EndCase "Br_BrushTests.TextureBrush_TranslateTransform_MovesTheTiles", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_BrushTests.TextureBrush_Transform_Get_ReturnsTheMatrix + Expect.EndCase "Br_BrushTests.TextureBrush_Transform_Get_ReturnsTheMatrix", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_BrushTests.TextureBrush_GetTransform_CopiesTheMatrix + Expect.EndCase "Br_BrushTests.TextureBrush_GetTransform_CopiesTheMatrix", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_CapTests.CustomLineCap_New_BaseInset_KeepsTheFraction + Expect.EndCase "Br_CapTests.CustomLineCap_New_BaseInset_KeepsTheFraction", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_CapTests.CustomLineCap_BaseInset_Property_TakesASingle + Expect.EndCase "Br_CapTests.CustomLineCap_BaseInset_Property_TakesASingle", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_CapTests.CustomLineCap_Properties_RoundTrip + Expect.EndCase "Br_CapTests.CustomLineCap_Properties_RoundTrip", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_CapTests.CustomLineCap_StrokeCap_SetsBothEnds + Expect.EndCase "Br_CapTests.CustomLineCap_StrokeCap_SetsBothEnds", Err.Number, Err.Description + Expect.Skip "Br_CapTests.CustomLineCap_StartStrokeCap_Let_Returns", "it ends in a native exception in version 0.9.45.0" + Expect.Skip "Br_CapTests.CustomLineCap_EndStrokeCap_Let_Returns", "it ends in a native exception in version 0.9.45.0" + Expect.Skip "Br_CapTests.CustomLineCap_SetStrokeCaps_WithACap_Returns", "it ends in a native exception in version 0.9.45.0" + Expect.StartCase + Err.Clear + Br_CapTests.CustomLineCap_SetStrokeCaps_WithNoCaps_ChangesNothing + Expect.EndCase "Br_CapTests.CustomLineCap_SetStrokeCaps_WithNoCaps_ChangesNothing", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_CapTests.CustomLineCap_EndCap_IsDrawn + Expect.EndCase "Br_CapTests.CustomLineCap_EndCap_IsDrawn", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_CapTests.CustomLineCap_BaseInset_EndsTheLineEarly + Expect.EndCase "Br_CapTests.CustomLineCap_BaseInset_EndsTheLineEarly", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_CapTests.ArrowCap_New_HeightComesBeforeWidth + Expect.EndCase "Br_CapTests.ArrowCap_New_HeightComesBeforeWidth", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_CapTests.ArrowCap_Properties_RoundTrip + Expect.EndCase "Br_CapTests.ArrowCap_Properties_RoundTrip", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_CapTests.ArrowCap_Clone_ReturnsAUsableCopy + Expect.EndCase "Br_CapTests.ArrowCap_Clone_ReturnsAUsableCopy", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_CapTests.ArrowCap_MiddleInset_Get_ReturnsTheSetting + Expect.EndCase "Br_CapTests.ArrowCap_MiddleInset_Get_ReturnsTheSetting", Err.Number, Err.Description + Expect.Skip "Br_CapTests.ArrowCap_StartStrokeCap_Let_Returns", "it ends in a native exception in version 0.9.45.0" + Expect.Skip "Br_CapTests.ArrowCap_EndStrokeCap_Let_Returns", "it ends in a native exception in version 0.9.45.0" + Expect.Skip "Br_CapTests.ArrowCap_SetStrokeCaps_WithACap_Returns", "it ends in a native exception in version 0.9.45.0" + Expect.StartCase + Err.Clear + Br_CapTests.ArrowCap_StrokeCap_SetsBothEnds + Expect.EndCase "Br_CapTests.ArrowCap_StrokeCap_SetsBothEnds", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_CapTests.ArrowCap_EndCap_IsDrawnAsAnArrowhead + Expect.EndCase "Br_CapTests.ArrowCap_EndCap_IsDrawnAsAnArrowhead", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_GradientBrushTests.Linear_LinearColors_HasTwoElements + Expect.EndCase "Br_GradientBrushTests.Linear_LinearColors_HasTwoElements", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_GradientBrushTests.Linear_LinearColors_ReturnsStartThenEnd + Expect.EndCase "Br_GradientBrushTests.Linear_LinearColors_ReturnsStartThenEnd", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_GradientBrushTests.Linear_LinearRGBs_HasTwoElements + Expect.EndCase "Br_GradientBrushTests.Linear_LinearRGBs_HasTwoElements", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_GradientBrushTests.Linear_LinearRGBs_AreCompleteARGBValues + Expect.EndCase "Br_GradientBrushTests.Linear_LinearRGBs_AreCompleteARGBValues", Err.Number, Err.Description + Expect.Skip "Br_GradientBrushTests.Linear_SetBlend_TwoArrays_Returns", "it ends in a native exception in version 0.9.45.0" + Expect.StartCase + Err.Clear + Br_GradientBrushTests.Linear_SetBlend_FirstElementsAndCount_SetsTheBlend + Expect.EndCase "Br_GradientBrushTests.Linear_SetBlend_FirstElementsAndCount_SetsTheBlend", Err.Number, Err.Description + Expect.Skip "Br_GradientBrushTests.Linear_SetInterpolationColors_TwoArrays_Returns", "it ends in a native exception in version 0.9.45.0" + Expect.StartCase + Err.Clear + Br_GradientBrushTests.Linear_SetInterpolationColors_FirstElementsAndCount_SetsTheColours + Expect.EndCase "Br_GradientBrushTests.Linear_SetInterpolationColors_FirstElementsAndCount_SetsTheColours", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_GradientBrushTests.Linear_BlendBellShape_PeaksAtTheFocus + Expect.EndCase "Br_GradientBrushTests.Linear_BlendBellShape_PeaksAtTheFocus", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_GradientBrushTests.Linear_WrapMode_Clamp_IsRefused + Expect.EndCase "Br_GradientBrushTests.Linear_WrapMode_Clamp_IsRefused", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_GradientBrushTests.Linear_WrapMode_TileRepeatsAndFlipMirrors + Expect.EndCase "Br_GradientBrushTests.Linear_WrapMode_TileRepeatsAndFlipMirrors", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_GradientBrushTests.Linear_Transform_Get_ReturnsTheMatrix + Expect.EndCase "Br_GradientBrushTests.Linear_Transform_Get_ReturnsTheMatrix", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_GradientBrushTests.Linear_GetTransform_CopiesTheMatrix + Expect.EndCase "Br_GradientBrushTests.Linear_GetTransform_CopiesTheMatrix", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_GradientBrushTests.Path_New_SinglePoint_ReportsTheFailure + Expect.EndCase "Br_GradientBrushTests.Path_New_SinglePoint_ReportsTheFailure", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_GradientBrushTests.Path_New_SinglePoint_LaterCallsFailWithInvalidParameter + Expect.EndCase "Br_GradientBrushTests.Path_New_SinglePoint_LaterCallsFailWithInvalidParameter", Err.Number, Err.Description + Expect.Skip "Br_GradientBrushTests.Path_SurroundColors_Get_ReturnsWhatWasSet", "it ends in a native exception in version 0.9.45.0" + Expect.StartCase + Err.Clear + Br_GradientBrushTests.Path_SurroundColors_OneColourPaintsTheWholeBoundary + Expect.EndCase "Br_GradientBrushTests.Path_SurroundColors_OneColourPaintsTheWholeBoundary", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_GradientBrushTests.Path_SurroundColors_TooManyColours_IsRefused + Expect.EndCase "Br_GradientBrushTests.Path_SurroundColors_TooManyColours_IsRefused", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_GradientBrushTests.Path_GraphicsPath_Get_IsNotImplemented + Expect.EndCase "Br_GradientBrushTests.Path_GraphicsPath_Get_IsNotImplemented", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_GradientBrushTests.Path_GraphicsPath_Let_IsNotImplemented + Expect.EndCase "Br_GradientBrushTests.Path_GraphicsPath_Let_IsNotImplemented", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_GradientBrushTests.Path_FocusScales_AndCenterPoint_RoundTrip + Expect.EndCase "Br_GradientBrushTests.Path_FocusScales_AndCenterPoint_RoundTrip", Err.Number, Err.Description + Expect.Skip "Br_GradientBrushTests.Path_SetBlend_Returns", "it ends in a native exception in version 0.9.45.0" + Expect.Skip "Br_GradientBrushTests.Path_SetInterpolationColors_Returns", "it ends in a native exception in version 0.9.45.0" + Expect.StartCase + Err.Clear + Br_GradientBrushTests.Path_GetInterpolationColors_WithoutPresetColours_IsWrongState + Expect.EndCase "Br_GradientBrushTests.Path_GetInterpolationColors_WithoutPresetColours_IsWrongState", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_GradientBrushTests.Path_BlendShapes_ReturnOk + Expect.EndCase "Br_GradientBrushTests.Path_BlendShapes_ReturnOk", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_GradientBrushTests.Path_WrapMode_ClampDrawsNothingOutsideAndTileRepeats + Expect.EndCase "Br_GradientBrushTests.Path_WrapMode_ClampDrawsNothingOutsideAndTileRepeats", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_GradientBrushTests.Path_Transform_Get_ReturnsTheMatrix + Expect.EndCase "Br_GradientBrushTests.Path_Transform_Get_ReturnsTheMatrix", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_GradientBrushTests.Path_FromGraphicsPath_CountsAllThePoints + Expect.EndCase "Br_GradientBrushTests.Path_FromGraphicsPath_CountsAllThePoints", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PathDataTests.New_Count_AndAllocate + Expect.EndCase "Br_PathDataTests.New_Count_AndAllocate", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PathDataTests.New_TypedPoints_CopiesThem + Expect.EndCase "Br_PathDataTests.New_TypedPoints_CopiesThem", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PathDataTests.Points_AndTypes_AreViewsFromIndexOne + Expect.EndCase "Br_PathDataTests.Points_AndTypes_AreViewsFromIndexOne", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PathDataTests.PointsPtr_AndTypesPtr_AreZeroWithoutRoom + Expect.EndCase "Br_PathDataTests.PointsPtr_AndTypesPtr_AreZeroWithoutRoom", Err.Number, Err.Description +#If Win64 Then + Expect.StartCase + Err.Clear + Br_PathDataTests.Point_Get_ReturnsThePoint + Expect.EndCase "Br_PathDataTests.Point_Get_ReturnsThePoint", Err.Number, Err.Description +#Else + Expect.Skip "Br_PathDataTests.Point_Get_ReturnsThePoint", "it ends in a native exception in version 0.9.45.0" +#End If +#If Win64 Then + Expect.StartCase + Err.Clear + Br_PathDataTests.TypedPoint_Get_ReturnsThePointAndItsType + Expect.EndCase "Br_PathDataTests.TypedPoint_Get_ReturnsThePointAndItsType", Err.Number, Err.Description +#Else + Expect.Skip "Br_PathDataTests.TypedPoint_Get_ReturnsThePointAndItsType", "it ends in a native exception in version 0.9.45.0" +#End If +#If Win64 Then + Expect.StartCase + Err.Clear + Br_PathDataTests.TypedPoints_Get_ReturnsACopy + Expect.EndCase "Br_PathDataTests.TypedPoints_Get_ReturnsACopy", Err.Number, Err.Description +#Else + Expect.Skip "Br_PathDataTests.TypedPoints_Get_ReturnsACopy", "it ends in a native exception in version 0.9.45.0" +#End If + Expect.StartCase + Err.Clear + Br_PathDataTests.Type_Get_OutOfRange_ReturnsZero + Expect.EndCase "Br_PathDataTests.Type_Get_OutOfRange_ReturnsZero", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PathDataTests.Point_Let_OutOfRange_LeavesTheMemoryBesideTheArrayAlone + Expect.EndCase "Br_PathDataTests.Point_Let_OutOfRange_LeavesTheMemoryBesideTheArrayAlone", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PathDataTests.Type_Let_OutOfRange_LeavesTheMemoryBesideTheArrayAlone + Expect.EndCase "Br_PathDataTests.Type_Let_OutOfRange_LeavesTheMemoryBesideTheArrayAlone", Err.Number, Err.Description + Expect.Skip "Br_PathDataTests.TypedPoint_Let_OutOfRange_LeavesTheMemoryBesideTheArraysAlone", "it ends in a native exception in version 0.9.45.0" + Expect.StartCase + Err.Clear + Br_PathDataTests.PathPointType_Properties_ReadTheByte + Expect.EndCase "Br_PathDataTests.PathPointType_Properties_ReadTheByte", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PathDataTests.PathPointType_ReadThroughAVariable_OfAnArrayElement + Expect.EndCase "Br_PathDataTests.PathPointType_ReadThroughAVariable_OfAnArrayElement", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PathIteratorTests.Count_SubpathCount_AndHasCurve + Expect.EndCase "Br_PathIteratorTests.Count_SubpathCount_AndHasCurve", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PathIteratorTests.NextSubpath_UDT_ListsTheFigures + Expect.EndCase "Br_PathIteratorTests.NextSubpath_UDT_ListsTheFigures", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PathIteratorTests.NextSubpath_Direct_ListsTheFigures + Expect.EndCase "Br_PathIteratorTests.NextSubpath_Direct_ListsTheFigures", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PathIteratorTests.Rewind_StartsAgain + Expect.EndCase "Br_PathIteratorTests.Rewind_StartsAgain", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PathIteratorTests.NextSubpathPath_UDT_ReturnsTheFigure + Expect.EndCase "Br_PathIteratorTests.NextSubpathPath_UDT_ReturnsTheFigure", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PathIteratorTests.NextSubpathPath_Direct_ReturnsTheFigure + Expect.EndCase "Br_PathIteratorTests.NextSubpathPath_Direct_ReturnsTheFigure", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PathIteratorTests.NextMarker_UDT_ListsTheSections + Expect.EndCase "Br_PathIteratorTests.NextMarker_UDT_ListsTheSections", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PathIteratorTests.NextMarker_Direct_ListsTheSections + Expect.EndCase "Br_PathIteratorTests.NextMarker_Direct_ListsTheSections", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PathIteratorTests.NextMarkerPath_UDT_ReturnsTheSection + Expect.EndCase "Br_PathIteratorTests.NextMarkerPath_UDT_ReturnsTheSection", Err.Number, Err.Description + Expect.Skip "Br_PathIteratorTests.NextMarkerPath_Direct_ReturnsTheCount", "it ends in a native exception in version 0.9.45.0" + Expect.Skip "Br_PathIteratorTests.NextMarkerPath_Direct_ReturnsAUsablePath", "it ends in a native exception in version 0.9.45.0" + Expect.StartCase + Err.Clear + Br_PathIteratorTests.NextPathType_UDT_ListsTheRunsOfAFigure + Expect.EndCase "Br_PathIteratorTests.NextPathType_UDT_ListsTheRunsOfAFigure", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PathIteratorTests.NextPathType_BeforeNextSubpath_ReturnsZero + Expect.EndCase "Br_PathIteratorTests.NextPathType_BeforeNextSubpath_ReturnsZero", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PathIteratorTests.Enumerate_UDT_CopiesTheWholePathOrAPart + Expect.EndCase "Br_PathIteratorTests.Enumerate_UDT_CopiesTheWholePathOrAPart", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PathIteratorTests.Enumerate_Direct_SizesTheArraysFromOne + Expect.EndCase "Br_PathIteratorTests.Enumerate_Direct_SizesTheArraysFromOne", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PathIteratorTests.Enumerate_OnlyOneIndex_IsRefused + Expect.EndCase "Br_PathIteratorTests.Enumerate_OnlyOneIndex_IsRefused", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PathIteratorTests.Enumerate_EmptyPath_RaisesUnspecifiedError + Expect.EndCase "Br_PathIteratorTests.Enumerate_EmptyPath_RaisesUnspecifiedError", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PathIteratorTests.CopyData_DoesWhatEnumerateDoes + Expect.EndCase "Br_PathIteratorTests.CopyData_DoesWhatEnumerateDoes", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PathTests.New_PathData_CreatesThePath + Expect.EndCase "Br_PathTests.New_PathData_CreatesThePath", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PathTests.New_TypedPoints_CreatesThePath + Expect.EndCase "Br_PathTests.New_TypedPoints_CreatesThePath", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PathTests.New_PointsAndTypes_CreatesThePath + Expect.EndCase "Br_PathTests.New_PointsAndTypes_CreatesThePath", Err.Number, Err.Description +#If Win64 Then + Expect.StartCase + Err.Clear + Br_PathTests.New_GpPointsAndTypes_CreatesThePath + Expect.EndCase "Br_PathTests.New_GpPointsAndTypes_CreatesThePath", Err.Number, Err.Description +#Else + Expect.Skip "Br_PathTests.New_GpPointsAndTypes_CreatesThePath", "it ends in a native exception in version 0.9.45.0" +#End If + Expect.StartCase + Err.Clear + Br_PathTests.PathData_Get_ReturnsThePoints + Expect.EndCase "Br_PathTests.PathData_Get_ReturnsThePoints", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PathTests.GetPoints_EmptyPath_RaisesUnspecifiedError + Expect.EndCase "Br_PathTests.GetPoints_EmptyPath_RaisesUnspecifiedError", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PathTests.GetTypes_EmptyPath_RaisesUnspecifiedError + Expect.EndCase "Br_PathTests.GetTypes_EmptyPath_RaisesUnspecifiedError", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PathTests.Types_OfTwoFiguresWithAClosedOneAndAMarker + Expect.EndCase "Br_PathTests.Types_OfTwoFiguresWithAClosedOneAndAMarker", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PathTests.Shapes_PointCounts + Expect.EndCase "Br_PathTests.Shapes_PointCounts", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PathTests.Lines_AddLines_AndBeziers_PointCounts + Expect.EndCase "Br_PathTests.Lines_AddLines_AndBeziers_PointCounts", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PathTests.AddBeziers_NeedsThreeNPlusOnePoints + Expect.EndCase "Br_PathTests.AddBeziers_NeedsThreeNPlusOnePoints", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PathTests.Curves_PointCounts + Expect.EndCase "Br_PathTests.Curves_PointCounts", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PathTests.Clone_IsIndependent + Expect.EndCase "Br_PathTests.Clone_IsIndependent", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PathTests.FillMode_WindingFillsTheInnerRectangleAlternateDoesNot + Expect.EndCase "Br_PathTests.FillMode_WindingFillsTheInnerRectangleAlternateDoesNot", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PathTests.LastPoint_IsTheEndOfTheLastFigure + Expect.EndCase "Br_PathTests.LastPoint_IsTheEndOfTheLastFigure", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PathTests.GetPoints_ListsThePointsFromIndexOne + Expect.EndCase "Br_PathTests.GetPoints_ListsThePointsFromIndexOne", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PathTests.Transform_MultipliesEveryPoint + Expect.EndCase "Br_PathTests.Transform_MultipliesEveryPoint", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PathTests.Flatten_ReplacesTheCurvesWithLines + Expect.EndCase "Br_PathTests.Flatten_ReplacesTheCurvesWithLines", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PathTests.Widen_ReplacesTheLineWithTheOutlineOfItsStroke + Expect.EndCase "Br_PathTests.Widen_ReplacesTheLineWithTheOutlineOfItsStroke", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PathTests.Outline_KeepsTheOuterBoundaryOfOverlappingFigures + Expect.EndCase "Br_PathTests.Outline_KeepsTheOuterBoundaryOfOverlappingFigures", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PathTests.Warp_MapsTheSourceRectangleToTheQuadrilateral + Expect.EndCase "Br_PathTests.Warp_MapsTheSourceRectangleToTheQuadrilateral", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PathTests.GetWorldBounds_OfARectangle + Expect.EndCase "Br_PathTests.GetWorldBounds_OfARectangle", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PathTests.IsVisible_AndIsOutlineVisible + Expect.EndCase "Br_PathTests.IsVisible_AndIsOutlineVisible", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PathTests.FillPath_FillsTheShape + Expect.EndCase "Br_PathTests.FillPath_FillsTheShape", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PenTests.New_WorldWidth_ScalesWithTheTransformation + Expect.EndCase "Br_PenTests.New_WorldWidth_ScalesWithTheTransformation", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PenTests.New_PixelWidth_IgnoresTheTransformation + Expect.EndCase "Br_PenTests.New_PixelWidth_IgnoresTheTransformation", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PenTests.New_Defaults_WidthOneAndSolid + Expect.EndCase "Br_PenTests.New_Defaults_WidthOneAndSolid", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PenTests.Clone_IsIndependent + Expect.EndCase "Br_PenTests.Clone_IsIndependent", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PenTests.Brush_ReturnsABrushOfTheSameClass + Expect.EndCase "Br_PenTests.Brush_ReturnsABrushOfTheSameClass", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PenTests.Color_NonSolidPen_ReadsZero_AndSettingMakesItSolid + Expect.EndCase "Br_PenTests.Color_NonSolidPen_ReadsZero_AndSettingMakesItSolid", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PenTests.Brush_AfterEarlierFailure_ReturnsTheBrush + Expect.EndCase "Br_PenTests.Brush_AfterEarlierFailure_ReturnsTheBrush", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PenTests.CustomStartCap_AfterEarlierFailure_ReturnsTheCap + Expect.EndCase "Br_PenTests.CustomStartCap_AfterEarlierFailure_ReturnsTheCap", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PenTests.Caps_StartEndAndDashCaps_AreSetTogetherOrApart + Expect.EndCase "Br_PenTests.Caps_StartEndAndDashCaps_AreSetTogetherOrApart", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PenTests.StartCap_Round_IsDrawnRoundAndTheEndFlat + Expect.EndCase "Br_PenTests.StartCap_Round_IsDrawnRoundAndTheEndFlat", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PenTests.CustomStartCap_CustomLineCap_ReturnsTheCap + Expect.EndCase "Br_PenTests.CustomStartCap_CustomLineCap_ReturnsTheCap", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PenTests.CustomStartCap_ArrowCap_ReturnsAUsableCap + Expect.EndCase "Br_PenTests.CustomStartCap_ArrowCap_ReturnsAUsableCap", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PenTests.CustomEndCap_Get_ReturnsTheCap + Expect.EndCase "Br_PenTests.CustomEndCap_Get_ReturnsTheCap", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PenTests.CustomEndCap_Get_WithACustomStartCap_ReturnsTheCap + Expect.EndCase "Br_PenTests.CustomEndCap_Get_WithACustomStartCap_ReturnsTheCap", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PenTests.DashStyle_Dash_HasTwoLengths_AndCustomPatternSetsCustom + Expect.EndCase "Br_PenTests.DashStyle_Dash_HasTwoLengths_AndCustomPatternSetsCustom", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PenTests.DashStyle_Dash_IsDrawnAsDashes + Expect.EndCase "Br_PenTests.DashStyle_Dash_IsDrawnAsDashes", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PenTests.CompoundArray_DrawsParallelLines + Expect.EndCase "Br_PenTests.CompoundArray_DrawsParallelLines", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PenTests.MiterLimit_BelowOneBecomesOne + Expect.EndCase "Br_PenTests.MiterLimit_BelowOneBecomesOne", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PenTests.Alignment_Inset_KeepsTheLineInsideTheShape + Expect.EndCase "Br_PenTests.Alignment_Inset_KeepsTheLineInsideTheShape", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PenTests.Transform_Get_ReturnsTheMatrix + Expect.EndCase "Br_PenTests.Transform_Get_ReturnsTheMatrix", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Br_PenTests.ScaleTransform_ThickensTheLine_AndResetTransformRestoresIt + Expect.EndCase "Br_PenTests.ScaleTransform_ThickensTheLine_AndResetTransformRestoresIt", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_EffectTests.Blur_ParametersGetWithoutSize_ReturnsTheParameters + Expect.EndCase "Fx_EffectTests.Blur_ParametersGetWithoutSize_ReturnsTheParameters", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_EffectTests.Sharpen_ParametersGetWithoutSize_ReturnsTheParameters + Expect.EndCase "Fx_EffectTests.Sharpen_ParametersGetWithoutSize_ReturnsTheParameters", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_EffectTests.BrightnessContrast_ParametersGetWithoutSize_ReturnsTheParameters + Expect.EndCase "Fx_EffectTests.BrightnessContrast_ParametersGetWithoutSize_ReturnsTheParameters", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_EffectTests.HueSaturationLightness_ParametersGetWithoutSize_ReturnsTheParameters + Expect.EndCase "Fx_EffectTests.HueSaturationLightness_ParametersGetWithoutSize_ReturnsTheParameters", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_EffectTests.Levels_ParametersGetWithoutSize_ReturnsTheParameters + Expect.EndCase "Fx_EffectTests.Levels_ParametersGetWithoutSize_ReturnsTheParameters", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_EffectTests.Tint_ParametersGetWithoutSize_ReturnsTheParameters + Expect.EndCase "Fx_EffectTests.Tint_ParametersGetWithoutSize_ReturnsTheParameters", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_EffectTests.ColorBalance_ParametersGetWithoutSize_ReturnsTheParameters + Expect.EndCase "Fx_EffectTests.ColorBalance_ParametersGetWithoutSize_ReturnsTheParameters", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_EffectTests.ColorCurve_ParametersGetWithoutSize_ReturnsTheParameters + Expect.EndCase "Fx_EffectTests.ColorCurve_ParametersGetWithoutSize_ReturnsTheParameters", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_EffectTests.ColorMatrixEffect_ParametersGetWithoutSize_ReturnsTheParameters + Expect.EndCase "Fx_EffectTests.ColorMatrixEffect_ParametersGetWithoutSize_ReturnsTheParameters", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_EffectTests.ColorLUT_ParametersGetWithoutSize_ReturnsTheParameters + Expect.EndCase "Fx_EffectTests.ColorLUT_ParametersGetWithoutSize_ReturnsTheParameters", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_EffectTests.RedEyeCorrection_ParametersGetWithoutSize_SetsNoError + Expect.EndCase "Fx_EffectTests.RedEyeCorrection_ParametersGetWithoutSize_SetsNoError", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_EffectTests.ParameterSize_BrightnessContrast_IsTheSizeOfItsParameters + Expect.EndCase "Fx_EffectTests.ParameterSize_BrightnessContrast_IsTheSizeOfItsParameters", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_EffectTests.ParameterSize_ColorMatrixEffect_IsTheSizeOfItsParameters + Expect.EndCase "Fx_EffectTests.ParameterSize_ColorMatrixEffect_IsTheSizeOfItsParameters", Err.Number, Err.Description + Expect.Skip "Fx_EffectTests.ColorBalance_NewWithParams_StoresTheParameters", "it ends in a native exception in version 0.9.45.0" + Expect.Skip "Fx_EffectTests.ColorCurve_NewWithParams_StoresTheParameters", "it ends in a native exception in version 0.9.45.0" + Expect.Skip "Fx_EffectTests.ColorLUT_NewWithParams_StoresTheParameters", "it ends in a native exception in version 0.9.45.0" + Expect.Skip "Fx_EffectTests.ColorMatrixEffect_NewWithParams_StoresTheMatrix", "it ends in a native exception in version 0.9.45.0" + Expect.Skip "Fx_EffectTests.HueSaturationLightness_NewWithParams_StoresTheParameters", "it ends in a native exception in version 0.9.45.0" + Expect.Skip "Fx_EffectTests.Levels_NewWithParams_StoresTheParameters", "it ends in a native exception in version 0.9.45.0" + Expect.StartCase + Err.Clear + Fx_EffectTests.ColorBalance_NewWithValues_StoresTheParameters + Expect.EndCase "Fx_EffectTests.ColorBalance_NewWithValues_StoresTheParameters", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_EffectTests.ColorCurve_NewWithValues_StoresTheParameters + Expect.EndCase "Fx_EffectTests.ColorCurve_NewWithValues_StoresTheParameters", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_EffectTests.HueSaturationLightness_NewWithValues_StoresTheParameters + Expect.EndCase "Fx_EffectTests.HueSaturationLightness_NewWithValues_StoresTheParameters", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_EffectTests.Levels_NewWithValues_StoresTheParameters + Expect.EndCase "Fx_EffectTests.Levels_NewWithValues_StoresTheParameters", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_EffectTests.ColorLUT_NewWithFourArrays_TakesTheAlphaTableFromLutA + Expect.EndCase "Fx_EffectTests.ColorLUT_NewWithFourArrays_TakesTheAlphaTableFromLutA", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_EffectTests.ColorLUTParams_FromFourArrays_TakesTheAlphaTableFromLutA + Expect.EndCase "Fx_EffectTests.ColorLUTParams_FromFourArrays_TakesTheAlphaTableFromLutA", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_EffectTests.ColorLUT_ApplyEffectWithFourArrays_KeepsAlphaWithTheIdentityTable + Expect.EndCase "Fx_EffectTests.ColorLUT_ApplyEffectWithFourArrays_KeepsAlphaWithTheIdentityTable", Err.Number, Err.Description +#If Win64 Then + Expect.Skip "Fx_EffectTests.RedEyeCorrection_NewWithParams_StoresTheAreas", "it ends in a native exception in version 0.9.45.0" +#Else + Expect.StartCase + Err.Clear + Fx_EffectTests.RedEyeCorrection_NewWithParams_StoresTheAreas + Expect.EndCase "Fx_EffectTests.RedEyeCorrection_NewWithParams_StoresTheAreas", Err.Number, Err.Description +#End If +#If Win64 Then + Expect.Skip "Fx_EffectTests.RedEyeCorrection_NewKeywordWithParams_StoresTheAreas", "it ends in a native exception in version 0.9.45.0" +#Else + Expect.StartCase + Err.Clear + Fx_EffectTests.RedEyeCorrection_NewKeywordWithParams_StoresTheAreas + Expect.EndCase "Fx_EffectTests.RedEyeCorrection_NewKeywordWithParams_StoresTheAreas", Err.Number, Err.Description +#End If + Expect.StartCase + Err.Clear + Fx_EffectTests.RedEyeCorrection_ParametersGet_AfterSettingAreas_ReturnsTheAreaCount + Expect.EndCase "Fx_EffectTests.RedEyeCorrection_ParametersGet_AfterSettingAreas_ReturnsTheAreaCount", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_EffectTests.RedEyeCorrection_ApplyEffectWithoutAreas_ReturnsInvalidParameter + Expect.EndCase "Fx_EffectTests.RedEyeCorrection_ApplyEffectWithoutAreas_ReturnsInvalidParameter", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_EffectTests.RedEyeCorrection_ParametersLet_StoresTheAreas + Expect.EndCase "Fx_EffectTests.RedEyeCorrection_ParametersLet_StoresTheAreas", Err.Number, Err.Description + Expect.Skip "Fx_EffectTests.RedEyeCorrectionParams_FromArray_CopiesTheAreas", "it ends in a native exception in version 0.9.45.0" + Expect.StartCase + Err.Clear + Fx_EffectTests.ColorMatrix_FirstIndexIsTheOutputComponent_ImageAttributes + Expect.EndCase "Fx_EffectTests.ColorMatrix_FirstIndexIsTheOutputComponent_ImageAttributes", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_EffectTests.ColorMatrix_FirstIndexIsTheOutputComponent_ColorMatrixEffect + Expect.EndCase "Fx_EffectTests.ColorMatrix_FirstIndexIsTheOutputComponent_ColorMatrixEffect", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_EffectTests.LevelsParams_HighightField_HoldsTheHighlightLevel + Expect.EndCase "Fx_EffectTests.LevelsParams_HighightField_HoldsTheHighlightLevel", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_FontCollectionTests.InstalledFontCollection_Create_ReturnsACollection + Expect.EndCase "Fx_FontCollectionTests.InstalledFontCollection_Create_ReturnsACollection", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_FontCollectionTests.InstalledFontCollection_New_ReturnsACollection + Expect.EndCase "Fx_FontCollectionTests.InstalledFontCollection_New_ReturnsACollection", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_FontCollectionTests.InstalledFontCollection_FamilyCount_CountsTheInstalledFamilies + Expect.EndCase "Fx_FontCollectionTests.InstalledFontCollection_FamilyCount_CountsTheInstalledFamilies", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_FontCollectionTests.InstalledFontCollection_Families_IsOneBasedAndHoldsAnArialFamily + Expect.EndCase "Fx_FontCollectionTests.InstalledFontCollection_Families_IsOneBasedAndHoldsAnArialFamily", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_FontCollectionTests.PrivateFontCollection_Create_ReturnsAnEmptyCollection + Expect.EndCase "Fx_FontCollectionTests.PrivateFontCollection_Create_ReturnsAnEmptyCollection", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_FontCollectionTests.PrivateFontCollection_New_ReturnsACollection + Expect.EndCase "Fx_FontCollectionTests.PrivateFontCollection_New_ReturnsACollection", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_FontCollectionTests.PrivateFontCollection_AddFontFile_AddsTheFamilyOfTheFile + Expect.EndCase "Fx_FontCollectionTests.PrivateFontCollection_AddFontFile_AddsTheFamilyOfTheFile", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_FontCollectionTests.PrivateFontCollection_AddFontFile_MissingFile_ReturnsFileNotFound + Expect.EndCase "Fx_FontCollectionTests.PrivateFontCollection_AddFontFile_MissingFile_ReturnsFileNotFound", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_FontCollectionTests.PrivateFontCollection_FontFromTheCollection_IsAvailable + Expect.EndCase "Fx_FontCollectionTests.PrivateFontCollection_FontFromTheCollection_IsAvailable", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_FontTests.FamilyName_OfAnInstalledFamily_EqualsTheNameGiven + Expect.EndCase "Fx_FontTests.FamilyName_OfAnInstalledFamily_EqualsTheNameGiven", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_FontTests.FamilyName_OfAGenericFamily_HasNoNulCharacter + Expect.EndCase "Fx_FontTests.FamilyName_OfAGenericFamily_HasNoNulCharacter", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_FontTests.FamilyName_WithTheNulRemoved_IsFoundByFontFamily + Expect.EndCase "Fx_FontTests.FamilyName_WithTheNulRemoved_IsFoundByFontFamily", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_FontTests.FamilyName_WithTheNul_IsFoundByFontFamilyAndFont + Expect.EndCase "Fx_FontTests.FamilyName_WithTheNul_IsFoundByFontFamilyAndFont", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_FontTests.FontFamily_UnknownName_IsNotAvailable + Expect.EndCase "Fx_FontTests.FontFamily_UnknownName_IsNotAvailable", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_FontTests.GenericFamilies_AreCreatedOnceAndAreAvailable + Expect.EndCase "Fx_FontTests.GenericFamilies_AreCreatedOnceAndAreAvailable", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_FontTests.Font_AscentAndLineSpacing_AreScaledFromTheDesignMetrics + Expect.EndCase "Fx_FontTests.Font_AscentAndLineSpacing_AreScaledFromTheDesignMetrics", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_FontTests.Font_FromAFamilyName_KeepsSizeStyleAndUnit + Expect.EndCase "Fx_FontTests.Font_FromAFamilyName_KeepsSizeStyleAndUnit", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_FontTests.Font_UnknownFamilyName_FallsBackToGenericSansSerif + Expect.EndCase "Fx_FontTests.Font_UnknownFamilyName_FallsBackToGenericSansSerif", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_FontTests.Font_FromAnUnavailableFamily_IsNotAvailable + Expect.EndCase "Fx_FontTests.Font_FromAnUnavailableFamily_IsNotAvailable", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_FontTests.Font_GetHeight_ScalesWithTheResolutionForPoints + Expect.EndCase "Fx_FontTests.Font_GetHeight_ScalesWithTheResolutionForPoints", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_FontTests.Font_GetLogFontW_GivesANegativeHeightForAPixelFont + Expect.EndCase "Fx_FontTests.Font_GetLogFontW_GivesANegativeHeightForAPixelFont", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_FontTests.Font_VariableDeclaredAsFont_HoldsAGdiPlusFont + Expect.EndCase "Fx_FontTests.Font_VariableDeclaredAsFont_HoldsAGdiPlusFont", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_StringFormatTests.GenericDefaultStringFormat_ChangeIsSeenByEveryLaterCall + Expect.EndCase "Fx_StringFormatTests.GenericDefaultStringFormat_ChangeIsSeenByEveryLaterCall", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_StringFormatTests.GenericDefaultStringFormat_CloneIsNotShared + Expect.EndCase "Fx_StringFormatTests.GenericDefaultStringFormat_CloneIsNotShared", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_StringFormatTests.GenericTypographicStringFormat_ChangeIsSeenByEveryLaterCall + Expect.EndCase "Fx_StringFormatTests.GenericTypographicStringFormat_ChangeIsSeenByEveryLaterCall", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_StringFormatTests.GenericFormats_HaveTheDocumentedSettings + Expect.EndCase "Fx_StringFormatTests.GenericFormats_HaveTheDocumentedSettings", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_StringFormatTests.Alignment_GetAfterSettingCenter_ReturnsCenter + Expect.EndCase "Fx_StringFormatTests.Alignment_GetAfterSettingCenter_ReturnsCenter", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_StringFormatTests.Alignment_GetAfterSettingFar_ReturnsFar + Expect.EndCase "Fx_StringFormatTests.Alignment_GetAfterSettingFar_ReturnsFar", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_StringFormatTests.Alignment_Get_LeavesLastResultOk + Expect.EndCase "Fx_StringFormatTests.Alignment_Get_LeavesLastResultOk", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_StringFormatTests.IsAlignmentCenter_AfterSettingCenter_IsTrue + Expect.EndCase "Fx_StringFormatTests.IsAlignmentCenter_AfterSettingCenter_IsTrue", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_StringFormatTests.IsAlignmentFar_AfterSettingFar_IsTrue + Expect.EndCase "Fx_StringFormatTests.IsAlignmentFar_AfterSettingFar_IsTrue", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_StringFormatTests.Alignment_SetFar_PlacesTheTextAtTheRightEdge + Expect.EndCase "Fx_StringFormatTests.Alignment_SetFar_PlacesTheTextAtTheRightEdge", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_StringFormatTests.GetTabStops_FirstForm_ReturnsTheFirstTabOffset + Expect.EndCase "Fx_StringFormatTests.GetTabStops_FirstForm_ReturnsTheFirstTabOffset", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_StringFormatTests.GetTabStops_FirstForm_ReturnsTheTabStops + Expect.EndCase "Fx_StringFormatTests.GetTabStops_FirstForm_ReturnsTheTabStops", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_StringFormatTests.GetTabStops_SecondForm_ReturnsTheFirstTabOffsetAndTheStops + Expect.EndCase "Fx_StringFormatTests.GetTabStops_SecondForm_ReturnsTheFirstTabOffsetAndTheStops", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_StringFormatTests.SetDigitSubstitutionUser_KeepsTheLanguage + Expect.EndCase "Fx_StringFormatTests.SetDigitSubstitutionUser_KeepsTheLanguage", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_StringFormatTests.SetDigitSubstitutionNone_KeepsTheLanguage + Expect.EndCase "Fx_StringFormatTests.SetDigitSubstitutionNone_KeepsTheLanguage", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_StringFormatTests.SetDigitSubstitutionNational_KeepsTheLanguage + Expect.EndCase "Fx_StringFormatTests.SetDigitSubstitutionNational_KeepsTheLanguage", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_StringFormatTests.SetDigitSubstitutionTraditional_KeepsTheLanguage + Expect.EndCase "Fx_StringFormatTests.SetDigitSubstitutionTraditional_KeepsTheLanguage", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_StringFormatTests.DigitSubstitutionLanguage_Let_KeepsTheMethod + Expect.EndCase "Fx_StringFormatTests.DigitSubstitutionLanguage_Let_KeepsTheMethod", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_StringFormatTests.DigitSubstitutionMethod_Let_KeepsTheLanguage + Expect.EndCase "Fx_StringFormatTests.DigitSubstitutionMethod_Let_KeepsTheLanguage", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_StringFormatTests.SetDigitSubstitution_SetsLanguageAndMethodTogether + Expect.EndCase "Fx_StringFormatTests.SetDigitSubstitution_SetsLanguageAndMethodTogether", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Fx_StringFormatTests.MeasurableCharacterRanges_MoreThan32_ReturnsValueOverflow + Expect.EndCase "Fx_StringFormatTests.MeasurableCharacterRanges_MoreThan32_ReturnsValueOverflow", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_BaseTests.LastResult_AfterAFailedCall_IsTheStatus + Expect.EndCase "Gr_BaseTests.LastResult_AfterAFailedCall_IsTheStatus", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_BaseTests.LastResult_ASuccessfulCall_DoesNotClearIt + Expect.EndCase "Gr_BaseTests.LastResult_ASuccessfulCall_DoesNotClearIt", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_BaseTests.GetLastResult_ReturnsTheStatusAndClearsIt + Expect.EndCase "Gr_BaseTests.GetLastResult_ReturnsTheStatusAndClearsIt", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_BaseTests.GdipErrorHandlingStrategy_Default_IgnoresErrors + Expect.EndCase "Gr_BaseTests.GdipErrorHandlingStrategy_Default_IgnoresErrors", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_BaseTests.RaiseErrors_AFailedCall_RaisesTheStatusPlusTheBase + Expect.EndCase "Gr_BaseTests.RaiseErrors_AFailedCall_RaisesTheStatusPlusTheBase", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_BaseTests.RaiseErrors_ACodeBase_IsAddedToTheStatus + Expect.EndCase "Gr_BaseTests.RaiseErrors_ACodeBase_IsAddedToTheStatus", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_BaseTests.HandleErrors_AFailedCall_CallsTheHandler + Expect.EndCase "Gr_BaseTests.HandleErrors_AFailedCall_CallsTheHandler", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_GraphicsTests.Graphics_NothingImage_ReportsError + Expect.EndCase "Gr_GraphicsTests.Graphics_NothingImage_ReportsError", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_GraphicsTests.NewGraphics_NothingImage_ReportsError + Expect.EndCase "Gr_GraphicsTests.NewGraphics_NothingImage_ReportsError", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_GraphicsTests.Graphics_NothingImage_DrawingFailsWithInvalidParameter + Expect.EndCase "Gr_GraphicsTests.Graphics_NothingImage_DrawingFailsWithInvalidParameter", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_GraphicsTests.Clear_VBColourRed_FillsRed + Expect.EndCase "Gr_GraphicsTests.Clear_VBColourRed_FillsRed", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_GraphicsTests.Clear_RGBFunction_FillsThatColour + Expect.EndCase "Gr_GraphicsTests.Clear_RGBFunction_FillsThatColour", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_GraphicsTests.Clear_CColorTransparent_KeepsAlpha + Expect.EndCase "Gr_GraphicsTests.Clear_CColorTransparent_KeepsAlpha", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_GraphicsTests.Clear_ColorForm_FillsExactColour + Expect.EndCase "Gr_GraphicsTests.Clear_ColorForm_FillsExactColour", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_GraphicsTests.DrawLine_WholeNumbers_DrawsBetweenThePoints + Expect.EndCase "Gr_GraphicsTests.DrawLine_WholeNumbers_DrawsBetweenThePoints", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_GraphicsTests.DrawString_PrefixedFont_Draws + Expect.EndCase "Gr_GraphicsTests.DrawString_PrefixedFont_Draws", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_GraphicsTests.Font_AssignedToAVariableOfTheStdoleType_Fails + Expect.EndCase "Gr_GraphicsTests.Font_AssignedToAVariableOfTheStdoleType_Fails", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_GraphicsTests.Font_AssignedToAGdiPlusFontVariable_IsUsable + Expect.EndCase "Gr_GraphicsTests.Font_AssignedToAGdiPlusFontVariable_IsUsable", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_GraphicsTests.DrawString_ClearTypeGridFitWithSourceCopy_ReturnsInvalidParameter + Expect.EndCase "Gr_GraphicsTests.DrawString_ClearTypeGridFitWithSourceCopy_ReturnsInvalidParameter", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_GraphicsTests.DrawDriverString_BrushVariableAndOptions_Draws + Expect.EndCase "Gr_GraphicsTests.DrawDriverString_BrushVariableAndOptions_Draws", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_GraphicsTests.DrawImage_DestRectAndSourceRect_DrawsThePart + Expect.EndCase "Gr_GraphicsTests.DrawImage_DestRectAndSourceRect_DrawsThePart", Err.Number, Err.Description +#If Win64 Then + Expect.StartCase + Err.Clear + Gr_GraphicsTests.DrawImage_DestRectF_ScalesTheImage + Expect.EndCase "Gr_GraphicsTests.DrawImage_DestRectF_ScalesTheImage", Err.Number, Err.Description +#Else + Expect.Skip "Gr_GraphicsTests.DrawImage_DestRectF_ScalesTheImage", "it ends in a native exception in version 0.9.45.0" +#End If +#If Win64 Then + Expect.StartCase + Err.Clear + Gr_GraphicsTests.DrawImage_DestRect_ScalesTheImage + Expect.EndCase "Gr_GraphicsTests.DrawImage_DestRect_ScalesTheImage", Err.Number, Err.Description +#Else + Expect.Skip "Gr_GraphicsTests.DrawImage_DestRect_ScalesTheImage", "it ends in a native exception in version 0.9.45.0" +#End If + Expect.Skip "Gr_GraphicsTests.DrawImage_ThreeFloatPointsAndSourcePart_DrawsTheImage", "it ends in a native exception in version 0.9.45.0" + Expect.StartCase + Err.Clear + Gr_GraphicsTests.DrawImage_ThreeIntegerPointsAndSourcePart_DrawsTheImage + Expect.EndCase "Gr_GraphicsTests.DrawImage_ThreeIntegerPointsAndSourcePart_DrawsTheImage", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_GraphicsTests.DrawImage_XYWidthHeight_ScalesTheImage + Expect.EndCase "Gr_GraphicsTests.DrawImage_XYWidthHeight_ScalesTheImage", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_GraphicsTests.Clip_Get_ReturnsTheClippingRegion + Expect.EndCase "Gr_GraphicsTests.Clip_Get_ReturnsTheClippingRegion", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_GraphicsTests.ClipBounds_AfterSetClip_ReturnsTheRectangle + Expect.EndCase "Gr_GraphicsTests.ClipBounds_AfterSetClip_ReturnsTheRectangle", Err.Number, Err.Description +#If Win64 Then + Expect.StartCase + Err.Clear + Gr_GraphicsTests.IsVisible_GpPointF_ReturnsWhetherThePointIsInside + Expect.EndCase "Gr_GraphicsTests.IsVisible_GpPointF_ReturnsWhetherThePointIsInside", Err.Number, Err.Description +#Else + Expect.Skip "Gr_GraphicsTests.IsVisible_GpPointF_ReturnsWhetherThePointIsInside", "it ends in a native exception in version 0.9.45.0" +#End If + Expect.StartCase + Err.Clear + Gr_GraphicsTests.IsVisible_Alternatives_Work + Expect.EndCase "Gr_GraphicsTests.IsVisible_Alternatives_Work", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_GraphicsTests.Transform_Get_ReturnsTheWorldTransformation + Expect.EndCase "Gr_GraphicsTests.Transform_Get_ReturnsTheWorldTransformation", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_GraphicsTests.GetTransform_CopiesTheWorldTransformation + Expect.EndCase "Gr_GraphicsTests.GetTransform_CopiesTheWorldTransformation", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_GraphicsTests.ShearTransform_ShearsTheCoordinates + Expect.EndCase "Gr_GraphicsTests.ShearTransform_ShearsTheCoordinates", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_GraphicsTests.MultiplyTransform_ShearMatrix_ShearsTheCoordinates + Expect.EndCase "Gr_GraphicsTests.MultiplyTransform_ShearMatrix_ShearsTheCoordinates", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_GraphicsTests.InterpolationMode_HighQuality_ReadsBackAsBicubic + Expect.EndCase "Gr_GraphicsTests.InterpolationMode_HighQuality_ReadsBackAsBicubic", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_GraphicsTests.InterpolationMode_LowQualityAndDefault_ReadBackAsBilinear + Expect.EndCase "Gr_GraphicsTests.InterpolationMode_LowQualityAndDefault_ReadBackAsBilinear", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_GraphicsTests.SmoothingMode_HighQuality_ReadsBackAsAntiAlias + Expect.EndCase "Gr_GraphicsTests.SmoothingMode_HighQuality_ReadsBackAsAntiAlias", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_GraphicsTests.SmoothingMode_HighSpeedAndDefault_ReadBackAsNone + Expect.EndCase "Gr_GraphicsTests.SmoothingMode_HighSpeedAndDefault_ReadBackAsNone", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_LibraryTests.GdiPlusUser_BitmapOfTwoWholeNumbers_IsCreated + Expect.EndCase "Gr_LibraryTests.GdiPlusUser_BitmapOfTwoWholeNumbers_IsCreated", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_LibraryTests.GdiPlusUser_BitmapOfSinglesOrWithFormat_IsCreated + Expect.EndCase "Gr_LibraryTests.GdiPlusUser_BitmapOfSinglesOrWithFormat_IsCreated", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_LibraryTests.GdiPlusUser_InstanceWhileRunning_WithInvalidInput_ReportsFailure + Expect.EndCase "Gr_LibraryTests.GdiPlusUser_InstanceWhileRunning_WithInvalidInput_ReportsFailure", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_LibraryTests.GdiPlusUser_ReleasingTheLastInstance_KeepsGdiPlusRunning + Expect.EndCase "Gr_LibraryTests.GdiPlusUser_ReleasingTheLastInstance_KeepsGdiPlusRunning", Err.Number, Err.Description + Expect.Skip "Gr_LibraryTests.TestControlForceBilinear_Method_ReturnsOk", "it ends in a native exception in version 0.9.45.0" + Expect.Skip "Gr_LibraryTests.TestControlNoICM_Method_ReturnsOk", "it ends in a native exception in version 0.9.45.0" + Expect.StartCase + Err.Clear + Gr_LibraryTests.TestControl_ModuleFunctions_ReturnOk + Expect.EndCase "Gr_LibraryTests.TestControl_ModuleFunctions_ReturnOk", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_LibraryTests.Alloc_ThroughAClass_ReturnsABlock + Expect.EndCase "Gr_LibraryTests.Alloc_ThroughAClass_ReturnsABlock", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_LibraryTests.Image_AssignedToAVariableOfTheVbType_Fails + Expect.EndCase "Gr_LibraryTests.Image_AssignedToAVariableOfTheVbType_Fails", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_LibraryTests.Image_AssignedToAGdiPlusImageVariable_IsUsable + Expect.EndCase "Gr_LibraryTests.Image_AssignedToAGdiPlusImageVariable_IsUsable", Err.Number, Err.Description + Expect.Skip "Gr_LibraryTests.SetAbort_ThenDrawingTheImage_Works", "it ends in a native exception in version 0.9.45.0" + Expect.StartCase + Err.Clear + Gr_LibraryTests.IsCanonicalPixelFormat_PremultipliedFormats_AreCanonical + Expect.EndCase "Gr_LibraryTests.IsCanonicalPixelFormat_PremultipliedFormats_AreCanonical", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_LibraryTests.GdipCreateStreamOnFile_ExistingFile_ReturnsAStream + Expect.EndCase "Gr_LibraryTests.GdipCreateStreamOnFile_ExistingFile_ReturnsAStream", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_LibraryTests.PropertyTag_ConstantsAbove8000_AreNotNegativeInLong + Expect.EndCase "Gr_LibraryTests.PropertyTag_ConstantsAbove8000_AreNotNegativeInLong", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_LibraryTests.RemovePropertyItem_HighPropertyTag_RemovesTheItem + Expect.EndCase "Gr_LibraryTests.RemovePropertyItem_HighPropertyTag_RemovesTheItem", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_LibraryTests.RemovePropertyItem_HighPropertyTagConvertedToLong_RemovesTheItem + Expect.EndCase "Gr_LibraryTests.RemovePropertyItem_HighPropertyTagConvertedToLong_RemovesTheItem", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_LibraryTests.GdiplusStartupParams_ReservedMembers_AreSingleBits + Expect.EndCase "Gr_LibraryTests.GdiplusStartupParams_ReservedMembers_AreSingleBits", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_LibraryTests.GdiplusStartupParams_ReservedMembers_FollowReserved0 + Expect.EndCase "Gr_LibraryTests.GdiplusStartupParams_ReservedMembers_FollowReserved0", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_LibraryTests.GpStatus_ImageFromMissingFile_IsInvalidParameter + Expect.EndCase "Gr_LibraryTests.GpStatus_ImageFromMissingFile_IsInvalidParameter", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_MatrixTests.Matrix_RectFAndPoint_MovesTheRectangleToThePoint + Expect.EndCase "Gr_MatrixTests.Matrix_RectFAndPoint_MovesTheRectangleToThePoint", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_MatrixTests.Matrix_RectAndPoint_MovesTheRectangleToThePoint + Expect.EndCase "Gr_MatrixTests.Matrix_RectAndPoint_MovesTheRectangleToThePoint", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_MatrixTests.OffsetX_Assign_ChangesOnlyDx + Expect.EndCase "Gr_MatrixTests.OffsetX_Assign_ChangesOnlyDx", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_MatrixTests.OffsetY_Assign_ChangesOnlyDy + Expect.EndCase "Gr_MatrixTests.OffsetY_Assign_ChangesOnlyDy", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_MatrixTests.Offset_Assign_ChangesBoth + Expect.EndCase "Gr_MatrixTests.Offset_Assign_ChangesBoth", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_MatrixTests.Scale_StatementWithParentheses_Scales + Expect.EndCase "Gr_MatrixTests.Scale_StatementWithParentheses_Scales", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_MatrixTests.Scale_OtherSpellings_Scale + Expect.EndCase "Gr_MatrixTests.Scale_OtherSpellings_Scale", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_MatrixTests.Transform_GetOnMatrix_ReturnsTheMatrix + Expect.EndCase "Gr_MatrixTests.Transform_GetOnMatrix_ReturnsTheMatrix", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_MatrixTests.Transform_GetOnGraphics_ThroughTheInterface_ReturnsTheMatrix + Expect.EndCase "Gr_MatrixTests.Transform_GetOnGraphics_ThroughTheInterface_ReturnsTheMatrix", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_MatrixTests.Transform_GetOnGradientBrush_ThroughTheInterface_ReturnsTheMatrix + Expect.EndCase "Gr_MatrixTests.Transform_GetOnGradientBrush_ThroughTheInterface_ReturnsTheMatrix", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_MatrixTests.Transform_Let_CopiesTheMatrix + Expect.EndCase "Gr_MatrixTests.Transform_Let_CopiesTheMatrix", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_MatrixTests.GetTransform_OnGraphics_ThroughTheInterface_CopiesTheTransformation + Expect.EndCase "Gr_MatrixTests.GetTransform_OnGraphics_ThroughTheInterface_CopiesTheTransformation", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_VBGraphicsTests.Line_ARGBColour_Draws + Expect.EndCase "Gr_VBGraphicsTests.Line_ARGBColour_Draws", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_VBGraphicsTests.Line_VBColourRed_DrawsRed + Expect.EndCase "Gr_VBGraphicsTests.Line_VBColourRed_DrawsRed", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_VBGraphicsTests.Line_VBColourWithoutTransparentAlpha_DrawsThatColour + Expect.EndCase "Gr_VBGraphicsTests.Line_VBColourWithoutTransparentAlpha_DrawsThatColour", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_VBGraphicsTests.Line_AlphaZeroColour_IsInvisibleWithTransparentAlpha + Expect.EndCase "Gr_VBGraphicsTests.Line_AlphaZeroColour_IsInvisibleWithTransparentAlpha", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_VBGraphicsTests.DrawWidth_BeforeFirstDraw_ReadsOne + Expect.EndCase "Gr_VBGraphicsTests.DrawWidth_BeforeFirstDraw_ReadsOne", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_VBGraphicsTests.DrawWidth_AssignedBeforeFirstDraw_IsKept + Expect.EndCase "Gr_VBGraphicsTests.DrawWidth_AssignedBeforeFirstDraw_IsKept", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_VBGraphicsTests.DrawWidth_AfterFirstDraw_CanBeChanged + Expect.EndCase "Gr_VBGraphicsTests.DrawWidth_AfterFirstDraw_CanBeChanged", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_VBGraphicsTests.Line_BoxFilled_FillsFromTheFirstCorner + Expect.EndCase "Gr_VBGraphicsTests.Line_BoxFilled_FillsFromTheFirstCorner", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_VBGraphicsTests.Line_BoxOutline_DrawsTheCorners + Expect.EndCase "Gr_VBGraphicsTests.Line_BoxOutline_DrawsTheCorners", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_VBGraphicsTests.Circle_StartAndEnd_DrawAnArc + Expect.EndCase "Gr_VBGraphicsTests.Circle_StartAndEnd_DrawAnArc", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_VBGraphicsTests.Circle_Full_DrawsTheOutline + Expect.EndCase "Gr_VBGraphicsTests.Circle_Full_DrawsTheOutline", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Gr_VBGraphicsTests.PSet_SetsOnlyThePixel + Expect.EndCase "Gr_VBGraphicsTests.PSet_SetsOnlyThePixel", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Im_AttributeTests.ColorMatrix_FirstIndexIsTheOutputComponent + Expect.EndCase "Im_AttributeTests.ColorMatrix_FirstIndexIsTheOutputComponent", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Im_AttributeTests.ColorMatrix_FifthColumnAddsAConstant + Expect.EndCase "Im_AttributeTests.ColorMatrix_FifthColumnAddsAConstant", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Im_AttributeTests.ColorKey_ColorFormKeysOutTheColour + Expect.EndCase "Im_AttributeTests.ColorKey_ColorFormKeysOutTheColour", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Im_AttributeTests.ColorKey_LongFormTakesAVbColour + Expect.EndCase "Im_AttributeTests.ColorKey_LongFormTakesAVbColour", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Im_AttributeTests.ColorKey_LongFormTakesRrggbbAsDocumented + Expect.EndCase "Im_AttributeTests.ColorKey_LongFormTakesRrggbbAsDocumented", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Im_BitmapTests.New_TwoIntegerArgumentsMakeABitmap + Expect.EndCase "Im_BitmapTests.New_TwoIntegerArgumentsMakeABitmap", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Im_BitmapTests.New_NewKeywordWithTwoIntegerArguments + Expect.EndCase "Im_BitmapTests.New_NewKeywordWithTwoIntegerArguments", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Im_BitmapTests.New_SingleArgumentsMakeABitmapOfThatSize + Expect.EndCase "Im_BitmapTests.New_SingleArgumentsMakeABitmapOfThatSize", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Im_BitmapTests.Clone_WholeBitmapWithItsRectangleKeepsPixels + Expect.EndCase "Im_BitmapTests.Clone_WholeBitmapWithItsRectangleKeepsPixels", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Im_BitmapTests.ConvertFormat_FixedBWWithABlankPalette + Expect.EndCase "Im_BitmapTests.ConvertFormat_FixedBWWithABlankPalette", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Im_BitmapTests.ConvertFormat_OptimalWithABlankPalette + Expect.EndCase "Im_BitmapTests.ConvertFormat_OptimalWithABlankPalette", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Im_BitmapTests.ConvertFormat_FixedPaletteTypeWithAFilledPalette + Expect.EndCase "Im_BitmapTests.ConvertFormat_FixedPaletteTypeWithAFilledPalette", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Im_BitmapTests.ConvertFormat_CustomPaletteFilledByTheProgram + Expect.EndCase "Im_BitmapTests.ConvertFormat_CustomPaletteFilledByTheProgram", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Im_BitmapTests.Load_PngWrittenThroughTheNativeCall + Expect.EndCase "Im_BitmapTests.Load_PngWrittenThroughTheNativeCall", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Im_CachedBitmapTests.Draw_OnGraphicsOfTheSameFormat + Expect.EndCase "Im_CachedBitmapTests.Draw_OnGraphicsOfTheSameFormat", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Im_CachedBitmapTests.Draw_OnGraphicsOfAnotherFormatIsWrongState + Expect.EndCase "Im_CachedBitmapTests.Draw_OnGraphicsOfAnotherFormatIsWrongState", Err.Number, Err.Description + Expect.Skip "Im_CodecTests.GetImageEncoders_ReturnsTheInstalledEncoders", "it does not return in version 0.9.45.0" + Expect.Skip "Im_CodecTests.GetImageDecoders_ReturnsTheInstalledDecoders", "it does not return in version 0.9.45.0" + Expect.StartCase + Err.Clear + Im_CodecTests.GetImageEncodersSize_ListsTheEncodersInABuffer + Expect.EndCase "Im_CodecTests.GetImageEncodersSize_ListsTheEncodersInABuffer", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Im_CodecTests.Text_PropertiesHaveNoTerminatingNull + Expect.EndCase "Im_CodecTests.Text_PropertiesHaveNoTerminatingNull", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Im_CodecTests.Text_PropertiesHoldTheTextBeforeTheNull + Expect.EndCase "Im_CodecTests.Text_PropertiesHoldTheTextBeforeTheNull", Err.Number, Err.Description +#If Win64 Then + Expect.StartCase + Err.Clear + Im_ImageTests.Save_EmptyParameterArrayWritesFile + Expect.EndCase "Im_ImageTests.Save_EmptyParameterArrayWritesFile", Err.Number, Err.Description +#Else + Expect.Skip "Im_ImageTests.Save_EmptyParameterArrayWritesFile", "it ends in a native exception in version 0.9.45.0" +#End If +#If Win64 Then + Expect.StartCase + Err.Clear + Im_ImageTests.Save_JpegQualityChangesFileSize + Expect.EndCase "Im_ImageTests.Save_JpegQualityChangesFileSize", Err.Number, Err.Description +#Else + Expect.Skip "Im_ImageTests.Save_JpegQualityChangesFileSize", "it ends in a native exception in version 0.9.45.0" +#End If +#If Win64 Then + Expect.StartCase + Err.Clear + Im_ImageTests.Save_ParameterArrayStartingAtOneIsRefused + Expect.EndCase "Im_ImageTests.Save_ParameterArrayStartingAtOneIsRefused", Err.Number, Err.Description +#Else + Expect.Skip "Im_ImageTests.Save_ParameterArrayStartingAtOneIsRefused", "it ends in a native exception in version 0.9.45.0" +#End If +#If Win64 Then + Expect.StartCase + Err.Clear + Im_ImageTests.SaveAdd_MultiFrameTiffHasTwoFrames + Expect.EndCase "Im_ImageTests.SaveAdd_MultiFrameTiffHasTwoFrames", Err.Number, Err.Description +#Else + Expect.Skip "Im_ImageTests.SaveAdd_MultiFrameTiffHasTwoFrames", "it ends in a native exception in version 0.9.45.0" +#End If + Expect.StartCase + Err.Clear + Im_ImageTests.Palette_LetChangesThePicture + Expect.EndCase "Im_ImageTests.Palette_LetChangesThePicture", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Im_ImageTests.PropertyItem_GetReturnsTheItem + Expect.EndCase "Im_ImageTests.PropertyItem_GetReturnsTheItem", Err.Number, Err.Description + Expect.Skip "Im_ImageTests.PropertyItem_PropertyOfTheReturnedElement", "it ends in a native exception in version 0.9.45.0" + Expect.StartCase + Err.Clear + Im_ImageTests.GetAllPropertyItems_ReturnsEveryItem + Expect.EndCase "Im_ImageTests.GetAllPropertyItems_ReturnsEveryItem", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Im_ImageTests.PropertyItem_RemoveTakesTheItemAway + Expect.EndCase "Im_ImageTests.PropertyItem_RemoveTakesTheItemAway", Err.Number, Err.Description + Expect.Skip "Im_ImageTests.GetEncoderParameterList_JpegListsTheQuality", "it ends in a native exception in version 0.9.45.0" + Expect.StartCase + Err.Clear + Im_ImageTests.BareImage_FunctionWorksUnqualified + Expect.EndCase "Im_ImageTests.BareImage_FunctionWorksUnqualified", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Im_ImageTests.BareImage_VariableCannotHoldABitmap + Expect.EndCase "Im_ImageTests.BareImage_VariableCannotHoldABitmap", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Im_ImageTests.Clone_OfABitmapIsAnImage + Expect.EndCase "Im_ImageTests.Clone_OfABitmapIsAnImage", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Im_ImageTests.Clone_WholeBitmapWithItsRectangle + Expect.EndCase "Im_ImageTests.Clone_WholeBitmapWithItsRectangle", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Im_MetafileTests.New_FromEmfHandlePlaysBack + Expect.EndCase "Im_MetafileTests.New_FromEmfHandlePlaysBack", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Im_MetafileTests.New_FromWmfHandleWithAPlaceableHeader + Expect.EndCase "Im_MetafileTests.New_FromWmfHandleWithAPlaceableHeader", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Im_MetafileTests.New_FromFileName + Expect.EndCase "Im_MetafileTests.New_FromFileName", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Im_MetafileTests.New_RecordingToFileNameWithReferenceDc + Expect.EndCase "Im_MetafileTests.New_RecordingToFileNameWithReferenceDc", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Im_MetafileTests.New_RecordingWithReferenceDcAndType + Expect.EndCase "Im_MetafileTests.New_RecordingWithReferenceDcAndType", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Im_MetafileTests.New_ReferenceDcAloneRecords + Expect.EndCase "Im_MetafileTests.New_ReferenceDcAloneRecords", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Im_MetafileTests.ConvertToEmfPlus_WithFileName + Expect.EndCase "Im_MetafileTests.ConvertToEmfPlus_WithFileName", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Im_MetafileTests.ConvertToEmfPlus_WithoutFileNameOrStream + Expect.EndCase "Im_MetafileTests.ConvertToEmfPlus_WithoutFileNameOrStream", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Im_MetafileTests.Image_LoadsAMetafileFile + Expect.EndCase "Im_MetafileTests.Image_LoadsAMetafileFile", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Im_TypeTests.GpPropertyItem_TheSByteReadsASignedByte + Expect.EndCase "Im_TypeTests.GpPropertyItem_TheSByteReadsASignedByte", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Im_TypeTests.GpPropertyItem_TheSShortReadsASignedShort + Expect.EndCase "Im_TypeTests.GpPropertyItem_TheSShortReadsASignedShort", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Im_TypeTests.GpPropertyItem_TheSLongReadsASignedLong + Expect.EndCase "Im_TypeTests.GpPropertyItem_TheSLongReadsASignedLong", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Im_TypeTests.GpPropertyItem_TheDoubleReadsADouble + Expect.EndCase "Im_TypeTests.GpPropertyItem_TheDoubleReadsADouble", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Im_TypeTests.GpPropertyItem_AsDoubleCoversTheSignedAndDoubleTypes + Expect.EndCase "Im_TypeTests.GpPropertyItem_AsDoubleCoversTheSignedAndDoubleTypes", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Im_TypeTests.GpPropertyItem_TheSRationalReadsASignedRational + Expect.EndCase "Im_TypeTests.GpPropertyItem_TheSRationalReadsASignedRational", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Im_TypeTests.GpPropertyItem_GetSRationalReadsASignedRational + Expect.EndCase "Im_TypeTests.GpPropertyItem_GetSRationalReadsASignedRational", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Im_TypeTests.GpPropertyItem_UnsignedReadersWork + Expect.EndCase "Im_TypeTests.GpPropertyItem_UnsignedReadersWork", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Im_TypeTests.ColorPalette_FunctionCopiesTheColours + Expect.EndCase "Im_TypeTests.ColorPalette_FunctionCopiesTheColours", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Im_TypeTests.ColorPalette_EntriesSetOneAtATimeAreKept + Expect.EndCase "Im_TypeTests.ColorPalette_EntriesSetOneAtATimeAreKept", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Im_TypeTests.ColorMask_AlphaShiftOfTheMaskedValueIsMinusOne + Expect.EndCase "Im_TypeTests.ColorMask_AlphaShiftOfTheMaskedValueIsMinusOne", Err.Number, Err.Description + Expect.Skip "Im_ZStackTests.Palette_GetReturnsThePalette", "it ends in a native exception in version 0.9.45.0" + Expect.StartCase + Err.Clear + Ty_CColorTests.Members_HaveTheCssValues + Expect.EndCase "Ty_CColorTests.Members_HaveTheCssValues", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_CColorTests.DarkSeaGreen_HasTheCssValue + Expect.EndCase "Ty_CColorTests.DarkSeaGreen_HasTheCssValue", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_CColorTests.Members_CanBeWrittenWithoutTheEnumerationName + Expect.EndCase "Ty_CColorTests.Members_CanBeWrittenWithoutTheEnumerationName", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_CColorTests.Color_KeepsAllFourBytesOfAMember + Expect.EndCase "Ty_CColorTests.Color_KeepsAllFourBytesOfAMember", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_CColorTests.SolidBrush_TransparentMemberDrawsNothing + Expect.EndCase "Ty_CColorTests.SolidBrush_TransparentMemberDrawsNothing", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_CColorTests.SolidBrush_ColorOfTransparentMemberDrawsNothing + Expect.EndCase "Ty_CColorTests.SolidBrush_ColorOfTransparentMemberDrawsNothing", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_CColorTests.SolidBrush_OpaqueMemberHasItsColour + Expect.EndCase "Ty_CColorTests.SolidBrush_OpaqueMemberHasItsColour", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_CColorTests.Pen_TransparentMemberDrawsNothing + Expect.EndCase "Ty_CColorTests.Pen_TransparentMemberDrawsNothing", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_CColorTests.Pen_ColorOfTransparentMemberDrawsNothing + Expect.EndCase "Ty_CColorTests.Pen_ColorOfTransparentMemberDrawsNothing", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_CColorTests.Clear_TransparentMemberClearsToTransparent + Expect.EndCase "Ty_CColorTests.Clear_TransparentMemberClearsToTransparent", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_CColorTests.Clear_ColorOfTransparentMemberClearsToTransparent + Expect.EndCase "Ty_CColorTests.Clear_ColorOfTransparentMemberClearsToTransparent", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_CColorTests.Clear_OpaqueMemberHasItsColour + Expect.EndCase "Ty_CColorTests.Clear_OpaqueMemberHasItsColour", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_CColorTests.Members_AreNotVisualBasicColours + Expect.EndCase "Ty_CColorTests.Members_AreNotVisualBasicColours", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_ColorTests.Color_HoldsOneLongField + Expect.EndCase "Ty_ColorTests.Color_HoldsOneLongField", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_ColorTests.Declared_IsTransparentBlackNotBlack + Expect.EndCase "Ty_ColorTests.Declared_IsTransparentBlackNotBlack", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_ColorTests.Declared_DrawsNothing + Expect.EndCase "Ty_ColorTests.Declared_DrawsNothing", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_ColorTests.Color_ChannelsAreMaskedWithoutError + Expect.EndCase "Ty_ColorTests.Color_ChannelsAreMaskedWithoutError", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_ColorTests.Color_FormsGiveTheDocumentedValues + Expect.EndCase "Ty_ColorTests.Color_FormsGiveTheDocumentedValues", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_ColorTests.Channels_GettersAndAliasesAgree + Expect.EndCase "Ty_ColorTests.Channels_GettersAndAliasesAgree", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_ColorTests.Channels_SetterOutOfRangeRaisesOverflowAndKeepsTheColour + Expect.EndCase "Ty_ColorTests.Channels_SetterOutOfRangeRaisesOverflowAndKeepsTheColour", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_ColorTests.Value_AndArgbFieldAreTheSame + Expect.EndCase "Ty_ColorTests.Value_AndArgbFieldAreTheSame", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_ColorTests.IsTransparent_IsTrueOnlyForTheExactValue + Expect.EndCase "Ty_ColorTests.IsTransparent_IsTrueOnlyForTheExactValue", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_ColorTests.RGB_And_BGR_ReturnTheTwoLayouts + Expect.EndCase "Ty_ColorTests.RGB_And_BGR_ReturnTheTwoLayouts", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_ColorTests.BGR_SetterTakesAColorrefAndMakesTheColourOpaque + Expect.EndCase "Ty_ColorTests.BGR_SetterTakesAColorrefAndMakesTheColourOpaque", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_ColorTests.BGR_SystemColourIsNotTranslated + Expect.EndCase "Ty_ColorTests.BGR_SystemColourIsNotTranslated", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_ColorTests.BGR_AfterOleTranslateColorGivesTheSystemColour + Expect.EndCase "Ty_ColorTests.BGR_AfterOleTranslateColorGivesTheSystemColour", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_ColorTests.SolidBrush_LongIsReadAsRGB + Expect.EndCase "Ty_ColorTests.SolidBrush_LongIsReadAsRGB", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_ColorTests.Pen_LongIsReadAsRGB + Expect.EndCase "Ty_ColorTests.Pen_LongIsReadAsRGB", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_ColorTests.Clear_LongIsReadAsRGB + Expect.EndCase "Ty_ColorTests.Clear_LongIsReadAsRGB", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_ColorTests.SolidBrush_LongMakesTheColourOpaque + Expect.EndCase "Ty_ColorTests.SolidBrush_LongMakesTheColourOpaque", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_ColorTests.SolidBrush_ColorWithBgrSetDrawsTheVisualBasicColour + Expect.EndCase "Ty_ColorTests.SolidBrush_ColorWithBgrSetDrawsTheVisualBasicColour", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_ColorTests.AsCOLORREF_ReturnsAColorref + Expect.EndCase "Ty_ColorTests.AsCOLORREF_ReturnsAColorref", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_ColorTests.FromCOLORREF_ReadsAColorref + Expect.EndCase "Ty_ColorTests.FromCOLORREF_ReadsAColorref", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_ColorTests.SetFromCOLORREF_ReadsAColorref + Expect.EndCase "Ty_ColorTests.SetFromCOLORREF_ReadsAColorref", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_ColorTests.Colorref_WorkaroundsConvertCorrectly + Expect.EndCase "Ty_ColorTests.Colorref_WorkaroundsConvertCorrectly", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_ColorTests.GetRGB_ReturnsTheChannelsInTheGivenType + Expect.EndCase "Ty_ColorTests.GetRGB_ReturnsTheChannelsInTheGivenType", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_ColorTests.GetRGBSAndD_ReturnPercentages + Expect.EndCase "Ty_ColorTests.GetRGBSAndD_ReturnPercentages", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_ColorTests.SetRGB_ClampsAndKeepsTheAlpha + Expect.EndCase "Ty_ColorTests.SetRGB_ClampsAndKeepsTheAlpha", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_ColorTests.SetRGBD_TakesPercentagesClampsAndKeepsTheAlpha + Expect.EndCase "Ty_ColorTests.SetRGBD_TakesPercentagesClampsAndKeepsTheAlpha", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_ColorTests.AsHex_ReturnsUpperCaseText + Expect.EndCase "Ty_ColorTests.AsHex_ReturnsUpperCaseText", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_ColorTests.GetHSL_ReturnsHueInDegreesAndPercentages + Expect.EndCase "Ty_ColorTests.GetHSL_ReturnsHueInDegreesAndPercentages", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_ColorTests.SetHSL_KeepsTheAlphaAndSetHSLASetsIt + Expect.EndCase "Ty_ColorTests.SetHSL_KeepsTheAlphaAndSetHSLASetsIt", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_ColorTests.RotatedHue_KeepsTheAlpha + Expect.EndCase "Ty_ColorTests.RotatedHue_KeepsTheAlpha", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_ColorTests.RotateHue_KeepsTheAlpha + Expect.EndCase "Ty_ColorTests.RotateHue_KeepsTheAlpha", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_ColorTests.RotatedHue_ByZeroKeepsTheAlpha + Expect.EndCase "Ty_ColorTests.RotatedHue_ByZeroKeepsTheAlpha", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_ColorTests.RotatedHue_ColoursAreTheRotatedOnesWhenTheAlphaIsSetAgain + Expect.EndCase "Ty_ColorTests.RotatedHue_ColoursAreTheRotatedOnesWhenTheAlphaIsSetAgain", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_ColorTests.Inverse_IsTheNegativeAndKeepsTheAlpha + Expect.EndCase "Ty_ColorTests.Inverse_IsTheNegativeAndKeepsTheAlpha", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_ColorTests.MakeARGB_MasksEachChannel + Expect.EndCase "Ty_ColorTests.MakeARGB_MasksEachChannel", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_ColorTests.HueToValue_FollowsTheHslAlgorithm + Expect.EndCase "Ty_ColorTests.HueToValue_FollowsTheHslAlgorithm", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpPointFTests.GpPointF_IsEightBytes + Expect.EndCase "Ty_GpPointFTests.GpPointF_IsEightBytes", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpPointFTests.GpPointF_FormsGiveTheDocumentedValues + Expect.EndCase "Ty_GpPointFTests.GpPointF_FormsGiveTheDocumentedValues", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpPointFTests.Zero_IsTrueOnlyForExactlyZero + Expect.EndCase "Ty_GpPointFTests.Zero_IsTrueOnlyForExactlyZero", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpPointFTests.Length_IsTheSquareRootOfTheSumOfSquares + Expect.EndCase "Ty_GpPointFTests.Length_IsTheSquareRootOfTheSumOfSquares", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpPointFTests.Length_SetScalesTheVectorKeepingItsDirection + Expect.EndCase "Ty_GpPointFTests.Length_SetScalesTheVectorKeepingItsDirection", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpPointFTests.Length_SetToZeroZeroesBothCoordinates + Expect.EndCase "Ty_GpPointFTests.Length_SetToZeroZeroesBothCoordinates", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpPointFTests.Length_SetOfAVectorShorterThanTwiceEpsilonChangesNothing + Expect.EndCase "Ty_GpPointFTests.Length_SetOfAVectorShorterThanTwiceEpsilonChangesNothing", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpPointFTests.Transposed_ExchangesTheCoordinates + Expect.EndCase "Ty_GpPointFTests.Transposed_ExchangesTheCoordinates", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpPointFTests.AsGpPoint_RoundsHalvesToEven + Expect.EndCase "Ty_GpPointFTests.AsGpPoint_RoundsHalvesToEven", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpPointFTests.SetFrom_TakesAGpPoint + Expect.EndCase "Ty_GpPointFTests.SetFrom_TakesAGpPoint", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpPointFTests.AsPOINT_RoundsAndSetFromPOINTConverts + Expect.EndCase "Ty_GpPointFTests.AsPOINT_RoundsAndSetFromPOINTConverts", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpPointFTests.Add_ChangesThePoint + Expect.EndCase "Ty_GpPointFTests.Add_ChangesThePoint", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpPointFTests.Sub_ChangesThePoint + Expect.EndCase "Ty_GpPointFTests.Sub_ChangesThePoint", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpPointFTests.Scale_MultipliesBothCoordinates + Expect.EndCase "Ty_GpPointFTests.Scale_MultipliesBothCoordinates", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpPointFTests.Scale_CallFormMultipliesBothCoordinates + Expect.EndCase "Ty_GpPointFTests.Scale_CallFormMultipliesBothCoordinates", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpPointFTests.Scale2_MultipliesEachCoordinateByItsOwnFactor + Expect.EndCase "Ty_GpPointFTests.Scale2_MultipliesEachCoordinateByItsOwnFactor", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpPointFTests.InvScale_And_InvScale2_Divide + Expect.EndCase "Ty_GpPointFTests.InvScale_And_InvScale2_Divide", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpPointFTests.Transpose_ExchangesTheCoordinatesOfThePoint + Expect.EndCase "Ty_GpPointFTests.Transpose_ExchangesTheCoordinatesOfThePoint", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpPointFTests.Normalize_SetsTheLengthToOneAndKeepsTheDirection + Expect.EndCase "Ty_GpPointFTests.Normalize_SetsTheLengthToOneAndKeepsTheDirection", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpPointFTests.Normalize_LeavesAZeroVector + Expect.EndCase "Ty_GpPointFTests.Normalize_LeavesAZeroVector", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpPointFTests.SumDiffProdQuot_ReturnNewPointsAndLeaveThePoint + Expect.EndCase "Ty_GpPointFTests.SumDiffProdQuot_ReturnNewPointsAndLeaveThePoint", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpPointFTests.DotProd_IsXTimesXPlusYTimesY + Expect.EndCase "Ty_GpPointFTests.DotProd_IsXTimesXPlusYTimesY", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpPointFTests.Equals_IsExact + Expect.EndCase "Ty_GpPointFTests.Equals_IsExact", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpPointTests.GpPoint_IsEightBytes + Expect.EndCase "Ty_GpPointTests.GpPoint_IsEightBytes", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpPointTests.GpPoint_FormsGiveTheDocumentedValues + Expect.EndCase "Ty_GpPointTests.GpPoint_FormsGiveTheDocumentedValues", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpPointTests.GpPoint_OfGpPointFRoundsHalvesToEven + Expect.EndCase "Ty_GpPointTests.GpPoint_OfGpPointFRoundsHalvesToEven", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpPointTests.Zero_IsTrueOnlyForBothCoordinatesZero + Expect.EndCase "Ty_GpPointTests.Zero_IsTrueOnlyForBothCoordinatesZero", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpPointTests.Transposed_ExchangesTheCoordinatesAndLeavesThePoint + Expect.EndCase "Ty_GpPointTests.Transposed_ExchangesTheCoordinatesAndLeavesThePoint", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpPointTests.AsGpPointF_ReturnsTheSameCoordinates + Expect.EndCase "Ty_GpPointTests.AsGpPointF_ReturnsTheSameCoordinates", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpPointTests.SetFrom_RoundsAGpPointF + Expect.EndCase "Ty_GpPointTests.SetFrom_RoundsAGpPointF", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpPointTests.AsPOINT_And_SetFromPOINT_ConvertToAndFromThePointStructure + Expect.EndCase "Ty_GpPointTests.AsPOINT_And_SetFromPOINT_ConvertToAndFromThePointStructure", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpPointTests.Add_ChangesThePoint + Expect.EndCase "Ty_GpPointTests.Add_ChangesThePoint", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpPointTests.Sub_ChangesThePoint + Expect.EndCase "Ty_GpPointTests.Sub_ChangesThePoint", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpPointTests.Transpose_ExchangesTheCoordinatesOfThePoint + Expect.EndCase "Ty_GpPointTests.Transpose_ExchangesTheCoordinatesOfThePoint", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpPointTests.SumAndDiff_ReturnNewPointsAndLeaveThePoint + Expect.EndCase "Ty_GpPointTests.SumAndDiff_ReturnNewPointsAndLeaveThePoint", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpPointTests.DotProd_IsXTimesXPlusYTimesY + Expect.EndCase "Ty_GpPointTests.DotProd_IsXTimesXPlusYTimesY", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpPointTests.DotProd_ProductAboveTheRangeOfALongIsReturned + Expect.EndCase "Ty_GpPointTests.DotProd_ProductAboveTheRangeOfALongIsReturned", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpPointTests.Equals_ComparesBothCoordinates + Expect.EndCase "Ty_GpPointTests.Equals_ComparesBothCoordinates", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectFTests.GpRectF_IsSixteenBytes + Expect.EndCase "Ty_GpRectFTests.GpRectF_IsSixteenBytes", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectFTests.GpRectF_FormsGiveTheDocumentedValues + Expect.EndCase "Ty_GpRectFTests.GpRectF_FormsGiveTheDocumentedValues", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectFTests.GpRectF_OfTheEdgesStructureTakesWidthAndHeightAsDifferences + Expect.EndCase "Ty_GpRectFTests.GpRectF_OfTheEdgesStructureTakesWidthAndHeightAsDifferences", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectFTests.GpRectFFrom_ReadsTheLongEdgesStructure + Expect.EndCase "Ty_GpRectFTests.GpRectFFrom_ReadsTheLongEdgesStructure", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectFTests.Location_And_Size_AreIndependent + Expect.EndCase "Ty_GpRectFTests.Location_And_Size_AreIndependent", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectFTests.Edges_AreXYAndXPlusWidthYPlusHeight + Expect.EndCase "Ty_GpRectFTests.Edges_AreXYAndXPlusWidthYPlusHeight", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectFTests.Corners_AreThePointsOfTheRectangle + Expect.EndCase "Ty_GpRectFTests.Corners_AreThePointsOfTheRectangle", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectFTests.IsEmptyArea_IsTrueForWidthOrHeightAtMostEpsilon + Expect.EndCase "Ty_GpRectFTests.IsEmptyArea_IsTrueForWidthOrHeightAtMostEpsilon", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectFTests.Diagonal_GetAndSetScaleWidthAndHeightKeepingTheLocation + Expect.EndCase "Ty_GpRectFTests.Diagonal_GetAndSetScaleWidthAndHeightKeepingTheLocation", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectFTests.Diagonal_SetOfARectangleShorterThanTwiceEpsilonChangesNothing + Expect.EndCase "Ty_GpRectFTests.Diagonal_SetOfARectangleShorterThanTwiceEpsilonChangesNothing", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectFTests.AsGpRect_RoundsAllFourValuesHalvesToEven + Expect.EndCase "Ty_GpRectFTests.AsGpRect_RoundsAllFourValuesHalvesToEven", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectFTests.SetFrom_TakesAGpRect + Expect.EndCase "Ty_GpRectFTests.SetFrom_TakesAGpRect", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectFTests.AsRECTF_And_ToRECTF_ReturnTheEdges + Expect.EndCase "Ty_GpRectFTests.AsRECTF_And_ToRECTF_ReturnTheEdges", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectFTests.AsRECT_RoundsEachEdge + Expect.EndCase "Ty_GpRectFTests.AsRECT_RoundsEachEdge", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectFTests.SetFromRECT_And_SetFromRECTF_SetPositionAndSize + Expect.EndCase "Ty_GpRectFTests.SetFromRECT_And_SetFromRECTF_SetPositionAndSize", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectFTests.Offset_And_OffsetPt_MoveTheRectangle + Expect.EndCase "Ty_GpRectFTests.Offset_And_OffsetPt_MoveTheRectangle", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectFTests.Inflate_And_InflatePt_MoveTheEdgesApart + Expect.EndCase "Ty_GpRectFTests.Inflate_And_InflatePt_MoveTheEdgesApart", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectFTests.Intersect_SetsTheIntersection + Expect.EndCase "Ty_GpRectFTests.Intersect_SetsTheIntersection", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectFTests.Intersect_OfRectanglesThatDoNotOverlapGivesNegativeSize + Expect.EndCase "Ty_GpRectFTests.Intersect_OfRectanglesThatDoNotOverlapGivesNegativeSize", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectFTests.Unite_SetsTheBoundingRectangle + Expect.EndCase "Ty_GpRectFTests.Unite_SetsTheBoundingRectangle", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectFTests.Unite_OfTwoEmptyRectanglesAtTheSameLocationIsFalse + Expect.EndCase "Ty_GpRectFTests.Unite_OfTwoEmptyRectanglesAtTheSameLocationIsFalse", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectFTests.Equals_IsExact + Expect.EndCase "Ty_GpRectFTests.Equals_IsExact", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectFTests.Contains2_And_ContainsPt_IncludeLeftAndTopEdgesButNotRightAndBottom + Expect.EndCase "Ty_GpRectFTests.Contains2_And_ContainsPt_IncludeLeftAndTopEdgesButNotRightAndBottom", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectFTests.Contains_CountsSharingAnEdgeAsWithin + Expect.EndCase "Ty_GpRectFTests.Contains_CountsSharingAnEdgeAsWithin", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectFTests.IntersectsWith_RectanglesThatOnlyTouchDoNotOverlap + Expect.EndCase "Ty_GpRectFTests.IntersectsWith_RectanglesThatOnlyTouchDoNotOverlap", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectFTests.IntersectedWith_And_UnitedWith_ReturnNewRectangles + Expect.EndCase "Ty_GpRectFTests.IntersectedWith_And_UnitedWith_ReturnNewRectangles", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectFTests.Intersection_And_Union_SetTheResult + Expect.EndCase "Ty_GpRectFTests.Intersection_And_Union_SetTheResult", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectTests.GpRect_IsSixteenBytes + Expect.EndCase "Ty_GpRectTests.GpRect_IsSixteenBytes", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectTests.GpRect_FormsGiveTheDocumentedValues + Expect.EndCase "Ty_GpRectTests.GpRect_FormsGiveTheDocumentedValues", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectTests.GpRect_OfGpRectFRoundsHalvesToEven + Expect.EndCase "Ty_GpRectTests.GpRect_OfGpRectFRoundsHalvesToEven", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectTests.GpRect_OfTheEdgesStructureTakesWidthAndHeightAsDifferences + Expect.EndCase "Ty_GpRectTests.GpRect_OfTheEdgesStructureTakesWidthAndHeightAsDifferences", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectTests.GpRectFrom_ReadsTheSingleEdgesStructure + Expect.EndCase "Ty_GpRectTests.GpRectFrom_ReadsTheSingleEdgesStructure", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectTests.Location_And_Size_AreIndependent + Expect.EndCase "Ty_GpRectTests.Location_And_Size_AreIndependent", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectTests.Edges_AreXYAndXPlusWidthYPlusHeight + Expect.EndCase "Ty_GpRectTests.Edges_AreXYAndXPlusWidthYPlusHeight", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectTests.Corners_AreThePointsOfTheRectangle + Expect.EndCase "Ty_GpRectTests.Corners_AreThePointsOfTheRectangle", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectTests.IsEmptyArea_IsTrueForZeroOrNegativeWidthOrHeight + Expect.EndCase "Ty_GpRectTests.IsEmptyArea_IsTrueForZeroOrNegativeWidthOrHeight", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectTests.AsGpRectF_ReturnsTheSameRectangle + Expect.EndCase "Ty_GpRectTests.AsGpRectF_ReturnsTheSameRectangle", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectTests.SetFrom_RoundsAGpRectF + Expect.EndCase "Ty_GpRectTests.SetFrom_RoundsAGpRectF", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectTests.AsRECT_AsRECTF_ToRECT_ReturnTheEdges + Expect.EndCase "Ty_GpRectTests.AsRECT_AsRECTF_ToRECT_ReturnTheEdges", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectTests.SetFromRECT_And_SetFromRECTF_SetPositionAndSize + Expect.EndCase "Ty_GpRectTests.SetFromRECT_And_SetFromRECTF_SetPositionAndSize", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectTests.Offset_And_OffsetPt_MoveTheRectangle + Expect.EndCase "Ty_GpRectTests.Offset_And_OffsetPt_MoveTheRectangle", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectTests.Inflate_And_InflatePt_MoveTheEdgesApart + Expect.EndCase "Ty_GpRectTests.Inflate_And_InflatePt_MoveTheEdgesApart", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectTests.Intersect_SetsTheIntersection + Expect.EndCase "Ty_GpRectTests.Intersect_SetsTheIntersection", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectTests.Intersect_OfRectanglesThatDoNotOverlapGivesNegativeSize + Expect.EndCase "Ty_GpRectTests.Intersect_OfRectanglesThatDoNotOverlapGivesNegativeSize", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectTests.Unite_SetsTheBoundingRectangle + Expect.EndCase "Ty_GpRectTests.Unite_SetsTheBoundingRectangle", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectTests.Unite_OfTwoEmptyRectanglesAtTheSameLocationIsFalse + Expect.EndCase "Ty_GpRectTests.Unite_OfTwoEmptyRectanglesAtTheSameLocationIsFalse", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectTests.Equals_ComparesPositionAndSize + Expect.EndCase "Ty_GpRectTests.Equals_ComparesPositionAndSize", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectTests.Contains2_And_ContainsPt_IncludeLeftAndTopEdgesButNotRightAndBottom + Expect.EndCase "Ty_GpRectTests.Contains2_And_ContainsPt_IncludeLeftAndTopEdgesButNotRightAndBottom", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectTests.Contains_CountsSharingAnEdgeAsWithin + Expect.EndCase "Ty_GpRectTests.Contains_CountsSharingAnEdgeAsWithin", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectTests.IntersectsWith_RectanglesThatOnlyTouchDoNotOverlap + Expect.EndCase "Ty_GpRectTests.IntersectsWith_RectanglesThatOnlyTouchDoNotOverlap", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectTests.IntersectedWith_And_UnitedWith_ReturnNewRectangles + Expect.EndCase "Ty_GpRectTests.IntersectedWith_And_UnitedWith_ReturnNewRectangles", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpRectTests.Intersection_And_Union_SetTheResult + Expect.EndCase "Ty_GpRectTests.Intersection_And_Union_SetTheResult", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpSizeFTests.GpSizeF_IsEightBytes + Expect.EndCase "Ty_GpSizeFTests.GpSizeF_IsEightBytes", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpSizeFTests.GpSizeF_FormsGiveTheDocumentedValues + Expect.EndCase "Ty_GpSizeFTests.GpSizeF_FormsGiveTheDocumentedValues", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpSizeFTests.GpSizeFFrom_ReadsTheSizeStructure + Expect.EndCase "Ty_GpSizeFTests.GpSizeFFrom_ReadsTheSizeStructure", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpSizeFTests.Empty_IsTrueOnlyForExactlyZero + Expect.EndCase "Ty_GpSizeFTests.Empty_IsTrueOnlyForExactlyZero", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpSizeFTests.Diagonal_GetAndSetScaleBothDimensions + Expect.EndCase "Ty_GpSizeFTests.Diagonal_GetAndSetScaleBothDimensions", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpSizeFTests.Diagonal_SetOfASizeShorterThanTwiceEpsilonChangesNothing + Expect.EndCase "Ty_GpSizeFTests.Diagonal_SetOfASizeShorterThanTwiceEpsilonChangesNothing", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpSizeFTests.AsGpSize_RoundsHalvesToEven + Expect.EndCase "Ty_GpSizeFTests.AsGpSize_RoundsHalvesToEven", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpSizeFTests.SetFrom_TakesAGpSize + Expect.EndCase "Ty_GpSizeFTests.SetFrom_TakesAGpSize", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpSizeFTests.AsSIZE_RoundsAndSetFromSIZEConverts + Expect.EndCase "Ty_GpSizeFTests.AsSIZE_RoundsAndSetFromSIZEConverts", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpSizeFTests.AsGpRectF_AsGpRect_AsRECT_ReturnARectangleAtTheOrigin + Expect.EndCase "Ty_GpSizeFTests.AsGpRectF_AsGpRect_AsRECT_ReturnARectangleAtTheOrigin", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpSizeFTests.Add_ChangesTheSize + Expect.EndCase "Ty_GpSizeFTests.Add_ChangesTheSize", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpSizeFTests.Sub_ChangesTheSize + Expect.EndCase "Ty_GpSizeFTests.Sub_ChangesTheSize", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpSizeFTests.Scale_MultipliesBothDimensions + Expect.EndCase "Ty_GpSizeFTests.Scale_MultipliesBothDimensions", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpSizeFTests.Scale_CallFormMultipliesBothDimensions + Expect.EndCase "Ty_GpSizeFTests.Scale_CallFormMultipliesBothDimensions", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpSizeFTests.Scale2_MultipliesEachDimensionByItsOwnFactor + Expect.EndCase "Ty_GpSizeFTests.Scale2_MultipliesEachDimensionByItsOwnFactor", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpSizeFTests.InvScale_And_InvScale2_Divide + Expect.EndCase "Ty_GpSizeFTests.InvScale_And_InvScale2_Divide", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpSizeFTests.SumDiffProdQuot_ReturnNewSizesAndLeaveTheSize + Expect.EndCase "Ty_GpSizeFTests.SumDiffProdQuot_ReturnNewSizesAndLeaveTheSize", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpSizeFTests.Equals_IsExact + Expect.EndCase "Ty_GpSizeFTests.Equals_IsExact", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpSizeTests.GpSize_IsEightBytes + Expect.EndCase "Ty_GpSizeTests.GpSize_IsEightBytes", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpSizeTests.GpSize_FormsGiveTheDocumentedValues + Expect.EndCase "Ty_GpSizeTests.GpSize_FormsGiveTheDocumentedValues", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpSizeTests.GpSize_OfGpSizeFRoundsHalvesToEven + Expect.EndCase "Ty_GpSizeTests.GpSize_OfGpSizeFRoundsHalvesToEven", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpSizeTests.GpSizeFrom_ReadsTheSizeStructure + Expect.EndCase "Ty_GpSizeTests.GpSizeFrom_ReadsTheSizeStructure", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpSizeTests.Empty_IsTrueOnlyForBothDimensionsZero + Expect.EndCase "Ty_GpSizeTests.Empty_IsTrueOnlyForBothDimensionsZero", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpSizeTests.AsGpSizeF_ReturnsTheSameDimensions + Expect.EndCase "Ty_GpSizeTests.AsGpSizeF_ReturnsTheSameDimensions", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpSizeTests.SetFrom_RoundsAGpSizeF + Expect.EndCase "Ty_GpSizeTests.SetFrom_RoundsAGpSizeF", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpSizeTests.AsSIZE_And_SetFromSIZE_ConvertToAndFromTheSizeStructure + Expect.EndCase "Ty_GpSizeTests.AsSIZE_And_SetFromSIZE_ConvertToAndFromTheSizeStructure", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpSizeTests.AsGpRect_AsGpRectF_AsRECT_ReturnARectangleAtTheOrigin + Expect.EndCase "Ty_GpSizeTests.AsGpRect_AsGpRectF_AsRECT_ReturnARectangleAtTheOrigin", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpSizeTests.Add_ChangesTheSize + Expect.EndCase "Ty_GpSizeTests.Add_ChangesTheSize", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpSizeTests.Sub_ChangesTheSize + Expect.EndCase "Ty_GpSizeTests.Sub_ChangesTheSize", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpSizeTests.SumAndDiff_ReturnNewSizesAndLeaveTheSize + Expect.EndCase "Ty_GpSizeTests.SumAndDiff_ReturnNewSizesAndLeaveTheSize", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_GpSizeTests.Equals_ComparesBothDimensions + Expect.EndCase "Ty_GpSizeTests.Equals_ComparesBothDimensions", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_StartupTests.Declared_VersionZeroIsRefused + Expect.EndCase "Ty_StartupTests.Declared_VersionZeroIsRefused", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_StartupTests.Fields_VersionTwoWithStartupParametersStarts + Expect.EndCase "Ty_StartupTests.Fields_VersionTwoWithStartupParametersStarts", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_StartupTests.SuppressBackgroundThread_InputOnlyConstructorFails + Expect.EndCase "Ty_StartupTests.SuppressBackgroundThread_InputOnlyConstructorFails", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_StartupTests.SuppressBackgroundThread_ConstructorWithOutputStarts + Expect.EndCase "Ty_StartupTests.SuppressBackgroundThread_ConstructorWithOutputStarts", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_StartupTests.Function_NoArgumentsGivesVersionOne + Expect.EndCase "Ty_StartupTests.Function_NoArgumentsGivesVersionOne", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_StartupTests.Function_StartupParametersFormGivesVersionTwo + Expect.EndCase "Ty_StartupTests.Function_StartupParametersFormGivesVersionTwo", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_StartupTests.Function_VersionFormGivesThatVersion + Expect.EndCase "Ty_StartupTests.Function_VersionFormGivesThatVersion", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_StartupTests.Output_HooksAreSetAfterStartingWithTheOutput + Expect.EndCase "Ty_StartupTests.Output_HooksAreSetAfterStartingWithTheOutput", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_StartupTests.ModuleFunctions_HookAndUnhookWithALongPtrToken + Expect.EndCase "Ty_StartupTests.ModuleFunctions_HookAndUnhookWithALongPtrToken", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_StartupTests.NotificationHookProc_HooksAndUnhooksWithTheFirstElementOfAnArray + Expect.EndCase "Ty_StartupTests.NotificationHookProc_HooksAndUnhooksWithTheFirstElementOfAnArray", Err.Number, Err.Description + Expect.StartCase + Err.Clear + Ty_StartupTests.NotificationHookProc_DoesNotWriteBeyondTheToken + Expect.EndCase "Ty_StartupTests.NotificationHookProc_DoesNotWriteBeyondTheToken", Err.Number, Err.Description + On Error GoTo 0 + Expect.EndRun + End Sub + +End Module diff --git a/test/package-tests/GdiPlus/Sources/Ty_CColorTests.twin b/test/package-tests/GdiPlus/Sources/Ty_CColorTests.twin new file mode 100644 index 00000000..cbe54630 --- /dev/null +++ b/test/package-tests/GdiPlus/Sources/Ty_CColorTests.twin @@ -0,0 +1,271 @@ +' Ty_CColorTests: the CColor enumeration (page Enumerations/CColor). The member values are those of the +' CSS named colours, with Transparent (&H00FFFFFF) added. +[TestFixture] +Module Ty_CColorTests + + Private Sub Member(ByVal Name As String, ByVal Actual As Long, ByVal Expected As Long) + Expect.AreEqual Ty_Hex8(Expected), Ty_Hex8(Actual), "CColor." & Name + End Sub + + [TestCase] + Public Sub Members_HaveTheCssValues() + Expect.Begin + Member "AliceBlue", CColor.AliceBlue, &HFFF0F8FF + Member "AntiqueWhite", CColor.AntiqueWhite, &HFFFAEBD7 + Member "Aqua", CColor.Aqua, &HFF00FFFF + Member "Aquamarine", CColor.Aquamarine, &HFF7FFFD4 + Member "Azure", CColor.Azure, &HFFF0FFFF + Member "Beige", CColor.Beige, &HFFF5F5DC + Member "Bisque", CColor.Bisque, &HFFFFE4C4 + Member "Black", CColor.Black, &HFF000000 + Member "BlanchedAlmond", CColor.BlanchedAlmond, &HFFFFEBCD + Member "Blue", CColor.Blue, &HFF0000FF + Member "BlueViolet", CColor.BlueViolet, &HFF8A2BE2 + Member "Brown", CColor.Brown, &HFFA52A2A + Member "BurlyWood", CColor.BurlyWood, &HFFDEB887 + Member "CadetBlue", CColor.CadetBlue, &HFF5F9EA0 + Member "Chartreuse", CColor.Chartreuse, &HFF7FFF00 + Member "Chocolate", CColor.Chocolate, &HFFD2691E + Member "Coral", CColor.Coral, &HFFFF7F50 + Member "CornflowerBlue", CColor.CornflowerBlue, &HFF6495ED + Member "Cornsilk", CColor.Cornsilk, &HFFFFF8DC + Member "Crimson", CColor.Crimson, &HFFDC143C + Member "Cyan", CColor.Cyan, &HFF00FFFF + Member "DarkBlue", CColor.DarkBlue, &HFF00008B + Member "DarkCyan", CColor.DarkCyan, &HFF008B8B + Member "DarkGoldenrod", CColor.DarkGoldenrod, &HFFB8860B + Member "DarkGray", CColor.DarkGray, &HFFA9A9A9 + Member "DarkGreen", CColor.DarkGreen, &HFF006400 + Member "DarkKhaki", CColor.DarkKhaki, &HFFBDB76B + Member "DarkMagenta", CColor.DarkMagenta, &HFF8B008B + Member "DarkOliveGreen", CColor.DarkOliveGreen, &HFF556B2F + Member "DarkOrange", CColor.DarkOrange, &HFFFF8C00 + Member "DarkOrchid", CColor.DarkOrchid, &HFF9932CC + Member "DarkRed", CColor.DarkRed, &HFF8B0000 + Member "DarkSalmon", CColor.DarkSalmon, &HFFE9967A + Member "DarkSlateBlue", CColor.DarkSlateBlue, &HFF483D8B + Member "DarkSlateGray", CColor.DarkSlateGray, &HFF2F4F4F + Member "DarkTurquoise", CColor.DarkTurquoise, &HFF00CED1 + Member "DarkViolet", CColor.DarkViolet, &HFF9400D3 + Member "DeepPink", CColor.DeepPink, &HFFFF1493 + Member "DeepSkyBlue", CColor.DeepSkyBlue, &HFF00BFFF + Member "DimGray", CColor.DimGray, &HFF696969 + Member "DodgerBlue", CColor.DodgerBlue, &HFF1E90FF + Member "Firebrick", CColor.Firebrick, &HFFB22222 + Member "FloralWhite", CColor.FloralWhite, &HFFFFFAF0 + Member "ForestGreen", CColor.ForestGreen, &HFF228B22 + Member "Fuchsia", CColor.Fuchsia, &HFFFF00FF + Member "Gainsboro", CColor.Gainsboro, &HFFDCDCDC + Member "GhostWhite", CColor.GhostWhite, &HFFF8F8FF + Member "Gold", CColor.Gold, &HFFFFD700 + Member "Goldenrod", CColor.Goldenrod, &HFFDAA520 + Member "Gray", CColor.Gray, &HFF808080 + Member "Green", CColor.Green, &HFF008000 + Member "GreenYellow", CColor.GreenYellow, &HFFADFF2F + Member "Honeydew", CColor.Honeydew, &HFFF0FFF0 + Member "HotPink", CColor.HotPink, &HFFFF69B4 + Member "IndianRed", CColor.IndianRed, &HFFCD5C5C + Member "Indigo", CColor.Indigo, &HFF4B0082 + Member "Ivory", CColor.Ivory, &HFFFFFFF0 + Member "Khaki", CColor.Khaki, &HFFF0E68C + Member "Lavender", CColor.Lavender, &HFFE6E6FA + Member "LavenderBlush", CColor.LavenderBlush, &HFFFFF0F5 + Member "LawnGreen", CColor.LawnGreen, &HFF7CFC00 + Member "LemonChiffon", CColor.LemonChiffon, &HFFFFFACD + Member "LightBlue", CColor.LightBlue, &HFFADD8E6 + Member "LightCoral", CColor.LightCoral, &HFFF08080 + Member "LightCyan", CColor.LightCyan, &HFFE0FFFF + Member "LightGoldenrodYellow", CColor.LightGoldenrodYellow, &HFFFAFAD2 + Member "LightGray", CColor.LightGray, &HFFD3D3D3 + Member "LightGreen", CColor.LightGreen, &HFF90EE90 + Member "LightPink", CColor.LightPink, &HFFFFB6C1 + Member "LightSalmon", CColor.LightSalmon, &HFFFFA07A + Member "LightSeaGreen", CColor.LightSeaGreen, &HFF20B2AA + Member "LightSkyBlue", CColor.LightSkyBlue, &HFF87CEFA + Member "LightSlateGray", CColor.LightSlateGray, &HFF778899 + Member "LightSteelBlue", CColor.LightSteelBlue, &HFFB0C4DE + Member "LightYellow", CColor.LightYellow, &HFFFFFFE0 + Member "Lime", CColor.Lime, &HFF00FF00 + Member "LimeGreen", CColor.LimeGreen, &HFF32CD32 + Member "Linen", CColor.Linen, &HFFFAF0E6 + Member "Magenta", CColor.Magenta, &HFFFF00FF + Member "Maroon", CColor.Maroon, &HFF800000 + Member "MediumAquamarine", CColor.MediumAquamarine, &HFF66CDAA + Member "MediumBlue", CColor.MediumBlue, &HFF0000CD + Member "MediumOrchid", CColor.MediumOrchid, &HFFBA55D3 + Member "MediumPurple", CColor.MediumPurple, &HFF9370DB + Member "MediumSeaGreen", CColor.MediumSeaGreen, &HFF3CB371 + Member "MediumSlateBlue", CColor.MediumSlateBlue, &HFF7B68EE + Member "MediumSpringGreen", CColor.MediumSpringGreen, &HFF00FA9A + Member "MediumTurquoise", CColor.MediumTurquoise, &HFF48D1CC + Member "MediumVioletRed", CColor.MediumVioletRed, &HFFC71585 + Member "MidnightBlue", CColor.MidnightBlue, &HFF191970 + Member "MintCream", CColor.MintCream, &HFFF5FFFA + Member "MistyRose", CColor.MistyRose, &HFFFFE4E1 + Member "Moccasin", CColor.Moccasin, &HFFFFE4B5 + Member "NavajoWhite", CColor.NavajoWhite, &HFFFFDEAD + Member "Navy", CColor.Navy, &HFF000080 + Member "OldLace", CColor.OldLace, &HFFFDF5E6 + Member "Olive", CColor.Olive, &HFF808000 + Member "OliveDrab", CColor.OliveDrab, &HFF6B8E23 + Member "Orange", CColor.Orange, &HFFFFA500 + Member "OrangeRed", CColor.OrangeRed, &HFFFF4500 + Member "Orchid", CColor.Orchid, &HFFDA70D6 + Member "PaleGoldenrod", CColor.PaleGoldenrod, &HFFEEE8AA + Member "PaleGreen", CColor.PaleGreen, &HFF98FB98 + Member "PaleTurquoise", CColor.PaleTurquoise, &HFFAFEEEE + Member "PaleVioletRed", CColor.PaleVioletRed, &HFFDB7093 + Member "PapayaWhip", CColor.PapayaWhip, &HFFFFEFD5 + Member "PeachPuff", CColor.PeachPuff, &HFFFFDAB9 + Member "Peru", CColor.Peru, &HFFCD853F + Member "Pink", CColor.Pink, &HFFFFC0CB + Member "Plum", CColor.Plum, &HFFDDA0DD + Member "PowderBlue", CColor.PowderBlue, &HFFB0E0E6 + Member "Purple", CColor.Purple, &HFF800080 + Member "Red", CColor.Red, &HFFFF0000 + Member "RosyBrown", CColor.RosyBrown, &HFFBC8F8F + Member "RoyalBlue", CColor.RoyalBlue, &HFF4169E1 + Member "SaddleBrown", CColor.SaddleBrown, &HFF8B4513 + Member "Salmon", CColor.Salmon, &HFFFA8072 + Member "SandyBrown", CColor.SandyBrown, &HFFF4A460 + Member "SeaGreen", CColor.SeaGreen, &HFF2E8B57 + Member "SeaShell", CColor.SeaShell, &HFFFFF5EE + Member "Sienna", CColor.Sienna, &HFFA0522D + Member "Silver", CColor.Silver, &HFFC0C0C0 + Member "SkyBlue", CColor.SkyBlue, &HFF87CEEB + Member "SlateBlue", CColor.SlateBlue, &HFF6A5ACD + Member "SlateGray", CColor.SlateGray, &HFF708090 + Member "Snow", CColor.Snow, &HFFFFFAFA + Member "SpringGreen", CColor.SpringGreen, &HFF00FF7F + Member "SteelBlue", CColor.SteelBlue, &HFF4682B4 + Member "Tan", CColor.Tan, &HFFD2B48C + Member "Teal", CColor.Teal, &HFF008080 + Member "Thistle", CColor.Thistle, &HFFD8BFD8 + Member "Tomato", CColor.Tomato, &HFFFF6347 + Member "Transparent", CColor.Transparent, &H00FFFFFF + Member "Turquoise", CColor.Turquoise, &HFF40E0D0 + Member "Violet", CColor.Violet, &HFFEE82EE + Member "Wheat", CColor.Wheat, &HFFF5DEB3 + Member "White", CColor.White, &HFFFFFFFF + Member "WhiteSmoke", CColor.WhiteSmoke, &HFFF5F5F5 + Member "Yellow", CColor.Yellow, &HFFFFFF00 + Member "YellowGreen", CColor.YellowGreen, &HFF9ACD32 + Expect.Done + End Sub + + ' CSS: darkseagreen is #8FBC8F. + [TestCase] + Public Sub DarkSeaGreen_HasTheCssValue() + Expect.Begin + Member "DarkSeaGreen", CColor.DarkSeaGreen, &HFF8FBC8F + Expect.Done + End Sub + + [TestCase] + Public Sub Members_CanBeWrittenWithoutTheEnumerationName() + Expect.Begin + Expect.AreEqual "FFFFA500", Ty_Hex8(Color(Orange).Value), "Value of Color(Orange)" + Expect.AreEqual "FFFFFFFF", Ty_Hex8(Color(White).Value), "Value of Color(White)" + Expect.Done + End Sub + + [TestCase] + Public Sub Color_KeepsAllFourBytesOfAMember() + Expect.Begin + Dim orange As Color = Color(CColor.Orange) + Expect.AreEqual "FFFFA500", Ty_Hex8(orange.Value), "Value of Color(CColor.Orange)" + Expect.AreEqual "255 255 165 0", orange.Alpha & " " & orange.Red & " " & orange.Green & " " & orange.Blue, "Alpha Red Green Blue of Color(CColor.Orange)" + Expect.AreEqual "0000A5FF", Ty_Hex8(orange.BGR), "BGR of Color(CColor.Orange)" + Dim transparent As Color = Color(CColor.Transparent) + Expect.AreEqual "00FFFFFF", Ty_Hex8(transparent.Value), "Value of Color(CColor.Transparent)" + Expect.Done + End Sub + + ' ---- A member given to a method that has a Long overload + + [TestCase] + Public Sub SolidBrush_TransparentMemberDrawsNothing() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim px As Color = Ty_FillOver(Color(0, 0, 0), SolidBrush(CColor.Transparent)) + Expect.AreEqual "FF000000", Ty_Hex8(px.Value), "pixel of a black bitmap after FillRectangle with SolidBrush(CColor.Transparent)" + Expect.Done + End Sub + + [TestCase] + Public Sub SolidBrush_ColorOfTransparentMemberDrawsNothing() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim px As Color = Ty_FillOver(Color(0, 0, 0), SolidBrush(Color(CColor.Transparent))) + Expect.AreEqual "FF000000", Ty_Hex8(px.Value), "pixel of a black bitmap after FillRectangle with SolidBrush(Color(CColor.Transparent))" + Expect.Done + End Sub + + [TestCase] + Public Sub SolidBrush_OpaqueMemberHasItsColour() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim red As Color = Ty_FillOver(Color(255, 255, 255), SolidBrush(CColor.Red)) + Expect.AreEqual "FFFF0000", Ty_Hex8(red.Value), "pixel after SolidBrush(CColor.Red)" + Dim orange As Color = Ty_FillOver(Color(255, 255, 255), SolidBrush(CColor.Orange)) + Expect.AreEqual "FFFFA500", Ty_Hex8(orange.Value), "pixel after SolidBrush(CColor.Orange)" + Expect.Done + End Sub + + [TestCase] + Public Sub Pen_TransparentMemberDrawsNothing() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim px As Color = Ty_LineOver(Color(0, 0, 0), Pen(CColor.Transparent)) + Expect.AreEqual "FF000000", Ty_Hex8(px.Value), "pixel of a black bitmap on the line of Pen(CColor.Transparent)" + Expect.Done + End Sub + + [TestCase] + Public Sub Pen_ColorOfTransparentMemberDrawsNothing() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim px As Color = Ty_LineOver(Color(0, 0, 0), Pen(Color(CColor.Transparent))) + Expect.AreEqual "FF000000", Ty_Hex8(px.Value), "pixel of a black bitmap on the line of Pen(Color(CColor.Transparent))" + Expect.Done + End Sub + + [TestCase] + Public Sub Clear_TransparentMemberClearsToTransparent() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim px As Color = Ty_ClearedWithLong(CColor.Transparent) + Expect.AreEqual 0, px.Alpha, "alpha of the pixel after Graphics.Clear CColor.Transparent" + Expect.Done + End Sub + + [TestCase] + Public Sub Clear_ColorOfTransparentMemberClearsToTransparent() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim px As Color = Ty_ClearedWith(Color(CColor.Transparent)) + Expect.AreEqual 0, px.Alpha, "alpha of the pixel after Graphics.Clear Color(CColor.Transparent)" + Expect.Done + End Sub + + [TestCase] + Public Sub Clear_OpaqueMemberHasItsColour() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Expect.AreEqual "FFFF0000", Ty_Hex8(Ty_ClearedWithLong(CColor.Red).Value), "pixel after Graphics.Clear CColor.Red" + Expect.Done + End Sub + + ' ---- A member is not a VB colour + + [TestCase] + Public Sub Members_AreNotVisualBasicColours() + Expect.Begin + Expect.IsTrue CColor.Red <> vbRed, "CColor.Red differs from vbRed" + Expect.AreEqual "FFFF0000", Ty_Hex8(CColor.Red), "CColor.Red (alpha, then red in the second byte)" + Expect.AreEqual "000000FF", Ty_Hex8(vbRed), "vbRed (red in the lowest byte)" + Expect.AreEqual Ty_Hex8(vbRed), Ty_Hex8(Color(CColor.Red).BGR), "BGR of Color(CColor.Red) against vbRed" + Expect.Done + End Sub + +End Module diff --git a/test/package-tests/GdiPlus/Sources/Ty_ColorTests.twin b/test/package-tests/GdiPlus/Sources/Ty_ColorTests.twin new file mode 100644 index 00000000..b128b2d3 --- /dev/null +++ b/test/package-tests/GdiPlus/Sources/Ty_ColorTests.twin @@ -0,0 +1,445 @@ +' Ty_ColorTests: the Color type, its module functions, and the way the Long overloads of the drawing +' methods read a colour (page Types/Color). +[TestFixture] +Module Ty_ColorTests + + Private Declare PtrSafe Function OleTranslateColor Lib "oleaut32" (ByVal clr As Long, ByVal hpal As LongPtr, ByRef colorref As Long) As Long + Private Declare PtrSafe Function GetSysColor Lib "user32" (ByVal nIndex As Long) As Long + + [TestCase] + Public Sub Color_HoldsOneLongField() + Expect.Begin + Dim c As Color + Expect.AreEqual 4, LenB(c), "LenB of a Color" + c.argb = &H11223344 + Expect.AreEqual "11223344", Ty_Hex8(c.Value), "Value after the field argb was set to &H11223344" + Expect.AreEqual "17 34 51 68", c.Alpha & " " & c.Red & " " & c.Green & " " & c.Blue, "Alpha Red Green Blue after argb was set to &H11223344: the top byte is alpha, then red, green and blue" + Expect.Done + End Sub + + ' ---- A declared colour + + [TestCase] + Public Sub Declared_IsTransparentBlackNotBlack() + Expect.Begin + Dim c As Color + Expect.AreEqual "00000000", Ty_Hex8(c.Value), "Value of a variable declared with Dim c As Color" + Expect.IsTrue c.IsNull, "IsNull of a variable declared with Dim c As Color" + Expect.AreEqual "FF000000", Ty_Hex8(Color().Value), "Value of Color() (opaque black)" + Expect.Done + End Sub + + [TestCase] + Public Sub Declared_DrawsNothing() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim c As Color + Dim sb As SolidBrush = New SolidBrush(c) + Dim px As Color = Ty_FillOver(Color(255, 255, 255), sb) + Expect.AreEqual "FFFFFFFF", Ty_Hex8(px.Value), "pixel of a white bitmap after FillRectangle with a SolidBrush of a declared Color" + Expect.Done + End Sub + + ' ---- Creating + + [TestCase] + Public Sub Color_ChannelsAreMaskedWithoutError() + Expect.Begin + On Error Resume Next + Err.Clear + Dim c As Color = Color(300, -1, 0, 256) + Dim errNumber As Long = Err.Number + On Error GoTo 0 + Expect.AreEqual "2CFF0000", Ty_Hex8(c.Value), "Value of Color(300, -1, 0, 256)" + Expect.AreEqual 0, errNumber, "Err.Number after Color(300, -1, 0, 256)" + Expect.Done + End Sub + + [TestCase] + Public Sub Color_FormsGiveTheDocumentedValues() + Expect.Begin + Expect.AreEqual "FF223344", Ty_Hex8(Color(&H22, &H33, &H44).Value), "Value of Color(&H22, &H33, &H44)" + Expect.AreEqual "11223344", Ty_Hex8(Color(&H11, &H22, &H33, &H44).Value), "Value of Color(&H11, &H22, &H33, &H44)" + Expect.AreEqual "80112233", Ty_Hex8(Color(&H80112233).Value), "Value of Color(&H80112233)" + Expect.AreEqual "FFFF0000", Ty_Hex8(Color(CColor.Red).Value), "Value of Color(CColor.Red)" + Expect.AreEqual "FF000000", Ty_Hex8(Color().Value), "Value of Color()" + Expect.IsTrue NoColor().IsNull, "NoColor().IsNull" + Expect.AreEqual "00000000", Ty_Hex8(NoColor().Value), "Value of NoColor()" + Expect.Done + End Sub + + ' ---- Channels + + [TestCase] + Public Sub Channels_GettersAndAliasesAgree() + Expect.Begin + Dim c As Color = Color(&H11, &H22, &H33, &H44) + Expect.AreEqual "17 34 51 68", c.Alpha & " " & c.Red & " " & c.Green & " " & c.Blue, "Alpha Red Green Blue of Color(&H11, &H22, &H33, &H44)" + Expect.AreEqual "17 34 51 68", c.A & " " & c.R & " " & c.G & " " & c.B, "A R G B of Color(&H11, &H22, &H33, &H44)" + c.Red = 200: c.Green = 100: c.Blue = 50: c.Alpha = 25 + Expect.AreEqual "19C86432", Ty_Hex8(c.Value), "Value after Red = 200, Green = 100, Blue = 50, Alpha = 25" + Expect.Done + End Sub + + [TestCase] + Public Sub Channels_SetterOutOfRangeRaisesOverflowAndKeepsTheColour() + Expect.Begin + Dim c As Color = Color(&H11, &H22, &H33, &H44) + Dim errNumber As Long + On Error Resume Next + Err.Clear + c.Alpha = 300 + errNumber = Err.Number + On Error GoTo 0 + Expect.AreEqual 6, errNumber, "Err.Number after Alpha = 300" + Expect.AreEqual "11223344", Ty_Hex8(c.Value), "Value after Alpha = 300 raised an error" + Expect.Done + End Sub + + ' ---- The whole value + + [TestCase] + Public Sub Value_AndArgbFieldAreTheSame() + Expect.Begin + Dim c As Color + c.Value = &H80FF0000 + Expect.AreEqual "80FF0000", Ty_Hex8(c.argb), "argb after Value = &H80FF0000" + Expect.IsFalse c.IsNull, "IsNull of &H80FF0000" + Expect.Done + End Sub + + [TestCase] + Public Sub IsTransparent_IsTrueOnlyForTheExactValue() + Expect.Begin + Expect.IsTrue Color(CColor.Transparent).IsTransparent, "IsTransparent of Color(CColor.Transparent)" + Expect.AreEqual "00FFFFFF", Ty_Hex8(Color(CColor.Transparent).Value), "Value of Color(CColor.Transparent)" + Expect.IsFalse Color(0, 0, 0, 0).IsTransparent, "IsTransparent of Color(0, 0, 0, 0)" + Expect.Done + End Sub + + ' ---- Windows colours + + [TestCase] + Public Sub RGB_And_BGR_ReturnTheTwoLayouts() + Expect.Begin + Dim c As Color = Color(&H22, &H33, &H44) + Expect.AreEqual "00223344", Ty_Hex8(c.RGB), "RGB of Color(&H22, &H33, &H44)" + Expect.AreEqual "00443322", Ty_Hex8(c.BGR), "BGR of Color(&H22, &H33, &H44)" + Expect.AreEqual Ty_Hex8(VBA.RGB(&H22, &H33, &H44)), Ty_Hex8(c.BGR), "BGR of Color(&H22, &H33, &H44) against VBA.RGB(&H22, &H33, &H44)" + Expect.Done + End Sub + + [TestCase] + Public Sub BGR_SetterTakesAColorrefAndMakesTheColourOpaque() + Expect.Begin + Dim c As Color + c.BGR = VBA.RGB(&H22, &H33, &H44) + Expect.AreEqual "FF223344", Ty_Hex8(c.Value), "Value after BGR = RGB(&H22, &H33, &H44)" + Dim red As Color + red.BGR = vbRed + Expect.AreEqual "FFFF0000", Ty_Hex8(red.Value), "Value after BGR = vbRed" + Expect.Done + End Sub + + ' A VB system colour is not a COLORREF: BGR keeps the colour numbers, so the colour that Windows uses is not the result. + [TestCase] + Public Sub BGR_SystemColourIsNotTranslated() + Expect.Begin + Dim c As Color + c.BGR = vbButtonFace + Dim buttonFace As Long = GetSysColor(15) + Dim windows As Color + windows.BGR = buttonFace + Expect.IsTrue c.Value <> windows.Value, "BGR = vbButtonFace gives the colour Windows uses for a button face (" & Ty_Hex8(c.Value) & ")" + Expect.AreEqual "FF0F0000", Ty_Hex8(c.Value), "Value after BGR = vbButtonFace (&H8000000F)" + Expect.Done + End Sub + + [TestCase] + Public Sub BGR_AfterOleTranslateColorGivesTheSystemColour() + Expect.Begin + Dim translated As Long + Expect.AreEqual 0, OleTranslateColor(vbButtonFace, 0, translated), "OleTranslateColor of vbButtonFace" + Dim c As Color + c.BGR = translated + Dim windows As Color = Color(255, GetSysColor(15) And &HFF, (GetSysColor(15) And &HFF00&) \ &H100, (GetSysColor(15) And &HFF0000) \ &H10000) + Expect.AreEqual Ty_Hex8(windows.Value), Ty_Hex8(c.Value), "Value after BGR = the OleTranslateColor result of vbButtonFace" + Expect.Done + End Sub + + ' The methods that take a plain Long colour read &H00RRGGBB and make it opaque. + [TestCase] + Public Sub SolidBrush_LongIsReadAsRGB() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim px As Color = Ty_FillOver(Color(255, 255, 255), SolidBrush(&H112233)) + Expect.AreEqual "FF112233", Ty_Hex8(px.Value), "pixel after FillRectangle with SolidBrush(&H112233)" + Dim blue As Color = Ty_FillOver(Color(255, 255, 255), SolidBrush(vbRed)) + Expect.AreEqual "FF0000FF", Ty_Hex8(blue.Value), "pixel after FillRectangle with SolidBrush(vbRed): blue, because the Long is read as &H00RRGGBB" + Expect.Done + End Sub + + [TestCase] + Public Sub Pen_LongIsReadAsRGB() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim px As Color = Ty_LineOver(Color(255, 255, 255), Pen(&H112233)) + Expect.AreEqual "FF112233", Ty_Hex8(px.Value), "pixel on the line of Pen(&H112233)" + Dim blue As Color = Ty_LineOver(Color(255, 255, 255), Pen(vbRed)) + Expect.AreEqual "FF0000FF", Ty_Hex8(blue.Value), "pixel on the line of Pen(vbRed): blue" + Expect.Done + End Sub + + [TestCase] + Public Sub Clear_LongIsReadAsRGB() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Expect.AreEqual "FF112233", Ty_Hex8(Ty_ClearedWithLong(&H112233).Value), "pixel after Graphics.Clear &H112233" + Expect.AreEqual "FF0000FF", Ty_Hex8(Ty_ClearedWithLong(vbRed).Value), "pixel after Graphics.Clear vbRed: blue" + Expect.Done + End Sub + + [TestCase] + Public Sub SolidBrush_LongMakesTheColourOpaque() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim px As Color = Ty_FillOver(Color(0, 0, 0), SolidBrush(&H80FF0000)) + Expect.AreEqual "FFFF0000", Ty_Hex8(px.Value), "pixel over black after SolidBrush(&H80FF0000): the alpha 80 is dropped, so the red is opaque" + Dim half As Color = Ty_FillOver(Color(0, 0, 0), New SolidBrush(Color(&H80FF0000))) + Expect.AreEqual "FF800000", Ty_Hex8(half.Value), "pixel over black after SolidBrush(Color(&H80FF0000)): half red" + Expect.Done + End Sub + + [TestCase] + Public Sub SolidBrush_ColorWithBgrSetDrawsTheVisualBasicColour() + Expect.Begin + Dim user As GdiPlusUser = GdiPlusUser() + Dim c As Color + c.BGR = vbRed + Dim px As Color = Ty_FillOver(Color(255, 255, 255), New SolidBrush(c)) + Expect.AreEqual "FFFF0000", Ty_Hex8(px.Value), "pixel after SolidBrush of a Color whose BGR was set to vbRed" + Dim viaColor As Color = Ty_FillOver(Color(255, 255, 255), New SolidBrush(Color(255, 0, 0))) + Expect.AreEqual "FFFF0000", Ty_Hex8(viaColor.Value), "pixel after SolidBrush(Color(255, 0, 0))" + Expect.Done + End Sub + + ' ---- AsCOLORREF, FromCOLORREF, SetFromCOLORREF + + [TestCase] + Public Sub AsCOLORREF_ReturnsAColorref() + Expect.Begin + Dim c As Color = Color(&H22, &H33, &H44) + Expect.AreEqual "00443322", Ty_Hex8(c.AsCOLORREF()), "AsCOLORREF of Color(&H22, &H33, &H44) (a COLORREF is &H00BBGGRR)" + Expect.AreEqual Ty_Hex8(VBA.RGB(&H22, &H33, &H44)), Ty_Hex8(c.AsCOLORREF()), "AsCOLORREF of Color(&H22, &H33, &H44) against VBA.RGB(&H22, &H33, &H44)" + Expect.Done + End Sub + + [TestCase] + Public Sub FromCOLORREF_ReadsAColorref() + Expect.Begin + Expect.AreEqual "FF223344", Ty_Hex8(FromCOLORREF(VBA.RGB(&H22, &H33, &H44)).Value), "Value of FromCOLORREF(RGB(&H22, &H33, &H44))" + Expect.AreEqual "FFFF0000", Ty_Hex8(FromCOLORREF(vbRed).Value), "Value of FromCOLORREF(vbRed)" + Expect.Done + End Sub + + [TestCase] + Public Sub SetFromCOLORREF_ReadsAColorref() + Expect.Begin + Dim c As Color + c.SetFromCOLORREF VBA.RGB(&H22, &H33, &H44) + Expect.AreEqual "FF223344", Ty_Hex8(c.Value), "Value after SetFromCOLORREF RGB(&H22, &H33, &H44)" + Dim red As Color + red.SetFromCOLORREF vbRed + Expect.AreEqual "FFFF0000", Ty_Hex8(red.Value), "Value after SetFromCOLORREF vbRed" + Expect.Done + End Sub + + [TestCase] + Public Sub Colorref_WorkaroundsConvertCorrectly() + Expect.Begin + Dim viaBgr As Color + viaBgr.BGR = VBA.RGB(&H22, &H33, &H44) + Expect.AreEqual "FF223344", Ty_Hex8(viaBgr.Value), "Value after BGR = RGB(&H22, &H33, &H44)" + Dim cr As Long = &H443322 + Dim byHand As Color = Color(255, cr And &HFF, (cr And &HFF00&) \ &H100, (cr And &HFF0000) \ &H10000) + Expect.AreEqual "FF223344", Ty_Hex8(byHand.Value), "Value of the hand-written conversion of the COLORREF &H443322" + Expect.Done + End Sub + + ' ---- Red, green and blue as numbers + + [TestCase] + Public Sub GetRGB_ReturnsTheChannelsInTheGivenType() + Expect.Begin + Dim c As Color = Color(&H80, 255, 100, 7) + Dim rb As Byte, gb As Byte, bb As Byte + c.GetRGBB rb, gb, bb + Expect.AreEqual "255 100 7", rb & " " & gb & " " & bb, "GetRGBB of Color(&H80, 255, 100, 7)" + Dim ri As Integer, gi As Integer, bi As Integer + c.GetRGBI ri, gi, bi + Expect.AreEqual "255 100 7", ri & " " & gi & " " & bi, "GetRGBI of Color(&H80, 255, 100, 7)" + Dim rl As Long, gl As Long, bl As Long + c.GetRGBL rl, gl, bl + Expect.AreEqual "255 100 7", rl & " " & gl & " " & bl, "GetRGBL of Color(&H80, 255, 100, 7)" + Expect.Done + End Sub + + [TestCase] + Public Sub GetRGBSAndD_ReturnPercentages() + Expect.Begin + Dim c As Color = Color(&H80, 255, 0, 51) + Dim rs As Single, gs As Single, bs As Single + c.GetRGBS rs, gs, bs + Ty_Near 100, rs, 0.001, "red of GetRGBS of Color(&H80, 255, 0, 51)" + Ty_Near 0, gs, 0.001, "green of GetRGBS of Color(&H80, 255, 0, 51)" + Ty_Near 20, bs, 0.001, "blue of GetRGBS of Color(&H80, 255, 0, 51)" + Dim rd As Double, gd As Double, bd As Double + c.GetRGBD rd, gd, bd + Ty_Near 100, rd, 0.001, "red of GetRGBD of Color(&H80, 255, 0, 51)" + Ty_Near 20, bd, 0.001, "blue of GetRGBD of Color(&H80, 255, 0, 51)" + Expect.Done + End Sub + + [TestCase] + Public Sub SetRGB_ClampsAndKeepsTheAlpha() + Expect.Begin + Dim c As Color = Color(&H80, 255, 0, 0) + c.SetRGB 300, -5, 128 + Expect.AreEqual "80FF0080", Ty_Hex8(c.Value), "Value after SetRGB 300, -5, 128 on a colour with alpha &H80" + Expect.Done + End Sub + + [TestCase] + Public Sub SetRGBD_TakesPercentagesClampsAndKeepsTheAlpha() + Expect.Begin + Dim c As Color = Color(&H80, 255, 255, 255) + c.SetRGBD 100, 0, 0 + Expect.AreEqual "80FF0000", Ty_Hex8(c.Value), "Value after SetRGBD 100, 0, 0 on a colour with alpha &H80" + c.SetRGBD 150, -20, 50 + Expect.AreEqual "80FF007F", Ty_Hex8(c.Value), "Value after SetRGBD 150, -20, 50 (clamped to 0 to 100)" + Expect.Done + End Sub + + ' ---- Hexadecimal text + + [TestCase] + Public Sub AsHex_ReturnsUpperCaseText() + Expect.Begin + Dim c As Color = Color(&H1A, &H2B, &H3C, &H4D) + Expect.AreEqual "#2B3C4D", c.AsHexRGB(), "AsHexRGB of Color(&H1A, &H2B, &H3C, &H4D)" + Expect.AreEqual "#2B3C4D1A", c.AsHexRGBA(), "AsHexRGBA of Color(&H1A, &H2B, &H3C, &H4D)" + Expect.Done + End Sub + + ' ---- Hue, saturation and lightness + + [TestCase] + Public Sub GetHSL_ReturnsHueInDegreesAndPercentages() + Expect.Begin + Dim orange As Color = Color(255, 128, 0) + Dim h As Single, s As Single, l As Single + orange.GetHSL h, s, l + Ty_Near 30, h, 0.6, "hue of GetHSL of Color(255, 128, 0)" + Ty_Near 100, s, 0.1, "saturation of GetHSL of Color(255, 128, 0)" + Ty_Near 50, l, 0.1, "lightness of GetHSL of Color(255, 128, 0)" + Dim a As Single + Dim half As Color = Color(&H80, 255, 128, 0) + half.GetHSLA h, s, l, a + Ty_Near 50, a, 1, "alpha of GetHSLA of a colour with alpha &H80" + Expect.Done + End Sub + + [TestCase] + Public Sub SetHSL_KeepsTheAlphaAndSetHSLASetsIt() + Expect.Begin + Dim c As Color = Color(255, 255, 255) + c.SetHSL 120, 100, 50 + Expect.AreEqual "FF00FF00", Ty_Hex8(c.Value), "Value after SetHSL 120, 100, 50 on an opaque colour" + c.SetHSLA 240, 100, 50, 100 + Expect.AreEqual "FF0000FF", Ty_Hex8(c.Value), "Value after SetHSLA 240, 100, 50, 100" + Dim declared As Color + declared.SetHSL 120, 100, 50 + Expect.AreEqual "0000FF00", Ty_Hex8(declared.Value), "Value after SetHSL on a variable declared with Dim (alpha stays 0)" + declared.SetHSLA 120, 100, 50, 100 + Expect.AreEqual "FF00FF00", Ty_Hex8(declared.Value), "Value after SetHSLA 120, 100, 50, 100 on the same variable" + Expect.Done + End Sub + + ' ---- RotatedHue, RotateHue + + [TestCase] + Public Sub RotatedHue_KeepsTheAlpha() + Expect.Begin + Dim green As Color = Color(255, 0, 0).RotatedHue(120) + Expect.AreEqual "FF00FF00", Ty_Hex8(green.Value), "Value of Color(255, 0, 0).RotatedHue(120)" + Expect.Done + End Sub + + [TestCase] + Public Sub RotateHue_KeepsTheAlpha() + Expect.Begin + Dim c As Color = Color(255, 0, 0) + c.RotateHue 120 + Expect.AreEqual "FF00FF00", Ty_Hex8(c.Value), "Value of Color(255, 0, 0) after RotateHue 120" + Expect.Done + End Sub + + [TestCase] + Public Sub RotatedHue_ByZeroKeepsTheAlpha() + Expect.Begin + Dim same As Color = Color(255, 0, 0).RotatedHue(0) + Expect.AreEqual "FFFF0000", Ty_Hex8(same.Value), "Value of Color(255, 0, 0).RotatedHue(0)" + Expect.Done + End Sub + + [TestCase] + Public Sub RotatedHue_ColoursAreTheRotatedOnesWhenTheAlphaIsSetAgain() + Expect.Begin + Dim red As Color = Color(255, 0, 0) + Dim green As Color = red.RotatedHue(120) + green.Alpha = red.Alpha + Expect.AreEqual "FF00FF00", Ty_Hex8(green.Value), "Value of RotatedHue(120) of red after its Alpha is set again" + Dim negative As Color = red.RotatedHue(-120) + negative.Alpha = 255 + Expect.AreEqual "FF0000FF", Ty_Hex8(negative.Value), "Value of RotatedHue(-120) of red after its Alpha is set to 255" + Dim over As Color = red.RotatedHue(480) + over.Alpha = 255 + Expect.AreEqual "FF00FF00", Ty_Hex8(over.Value), "Value of RotatedHue(480) of red after its Alpha is set to 255" + Expect.Done + End Sub + + ' ---- Inverting + + [TestCase] + Public Sub Inverse_IsTheNegativeAndKeepsTheAlpha() + Expect.Begin + Dim c As Color = Color(&H80, &H22, &H33, &H44) + Expect.AreEqual "80DDCCBB", Ty_Hex8(c.Inverse.Value), "Value of Inverse of Color(&H80, &H22, &H33, &H44)" + c.Invert + Expect.AreEqual "80DDCCBB", Ty_Hex8(c.Value), "Value after Invert" + Dim z As Color + z.Inverse = Color(10, 20, 30) + Expect.AreEqual "FFF5EBE1", Ty_Hex8(z.Value), "Value after Inverse = Color(10, 20, 30) on a declared variable" + Expect.Done + End Sub + + ' ---- Functions + + [TestCase] + Public Sub MakeARGB_MasksEachChannel() + Expect.Begin + Expect.AreEqual "01020304", Ty_Hex8(MakeARGB(1, 2, 3, 4)), "MakeARGB(1, 2, 3, 4)" + Expect.AreEqual "00FF0304", Ty_Hex8(MakeARGB(256, -1, 3, 4)), "MakeARGB(256, -1, 3, 4)" + Expect.Done + End Sub + + [TestCase] + Public Sub HueToValue_FollowsTheHslAlgorithm() + Expect.Begin + Ty_Near 0.56, HueToValue(0.1, 0.2, 0.8), 0.0001, "HueToValue(0.1, 0.2, 0.8)" + Ty_Near 0.8, HueToValue(0.5, 0.2, 0.8), 0.0001, "HueToValue(0.5, 0.2, 0.8)" + Ty_Near 0.2, HueToValue(0.9, 0.2, 0.8), 0.0001, "HueToValue(0.9, 0.2, 0.8)" + Expect.Done + End Sub + +End Module diff --git a/test/package-tests/GdiPlus/Sources/Ty_GpPointFTests.twin b/test/package-tests/GdiPlus/Sources/Ty_GpPointFTests.twin new file mode 100644 index 00000000..511968f3 --- /dev/null +++ b/test/package-tests/GdiPlus/Sources/Ty_GpPointFTests.twin @@ -0,0 +1,247 @@ +' Ty_GpPointFTests: the GpPointF type (page Types/GpPointF). +[TestFixture] +Module Ty_GpPointFTests + + [TestCase] + Public Sub GpPointF_IsEightBytes() + Expect.Begin + Dim p As GpPointF + Expect.AreEqual 8, LenB(p), "LenB of a GpPointF" + Expect.Done + End Sub + + ' ---- Creating + + [TestCase] + Public Sub GpPointF_FormsGiveTheDocumentedValues() + Expect.Begin + Dim p As GpPointF = GpPointF(3, 4) + Expect.AreEqual "3,4", p.X & "," & p.Y, "GpPointF(3, 4)" + Expect.IsTrue GpPointF().Zero, "Zero of GpPointF()" + Dim declared As GpPointF + Expect.IsTrue declared.Zero, "Zero of a variable declared with Dim" + Dim fromInt As GpPointF = GpPointF(GpPoint(7, 8)) + Expect.AreEqual "7,8", fromInt.X & "," & fromInt.Y, "GpPointF(GpPoint(7, 8))" + Dim fromSize As GpPointF = GpPointF(GpSizeF(1.5, 2.5)) + Expect.AreEqual "1.5,2.5", fromSize.X & "," & fromSize.Y, "GpPointF(GpSizeF(1.5, 2.5))" + Expect.Done + End Sub + + ' ---- Fields and properties + + [TestCase] + Public Sub Zero_IsTrueOnlyForExactlyZero() + Expect.Begin + Expect.IsTrue GpPointF(0, 0).Zero, "Zero of (0, 0)" + Expect.IsFalse GpPointF(0, 0.001).Zero, "Zero of (0, 0.001)" + Expect.Done + End Sub + + [TestCase] + Public Sub Length_IsTheSquareRootOfTheSumOfSquares() + Expect.Begin + Dim p As GpPointF = GpPointF(3, 4) + Ty_Near 5, p.Length, 0.0001, "Length of (3, 4)" + Expect.Done + End Sub + + [TestCase] + Public Sub Length_SetScalesTheVectorKeepingItsDirection() + Expect.Begin + Dim p As GpPointF = GpPointF(3, 4) + p.Length = 10 + Ty_Near 6, p.X, 0.0001, "X of (3, 4) after Length = 10" + Ty_Near 8, p.Y, 0.0001, "Y of (3, 4) after Length = 10" + Expect.Done + End Sub + + [TestCase] + Public Sub Length_SetToZeroZeroesBothCoordinates() + Expect.Begin + Dim p As GpPointF = GpPointF(3, 4) + p.Length = 0 + Expect.IsTrue p.Zero, "Zero of (3, 4) after Length = 0" + Expect.Done + End Sub + + [TestCase] + Public Sub Length_SetOfAVectorShorterThanTwiceEpsilonChangesNothing() + Expect.Begin + Dim zero As GpPointF + zero.Length = 5 + Expect.IsTrue zero.Zero, "Zero of (0, 0) after Length = 5" + Dim tiny As GpPointF = GpPointF(0.00000001!, 0) + tiny.Length = 5 + Ty_Near 0.00000001, tiny.X, 0.000000001, "X of (1E-8, 0) after Length = 5 (shorter than 2 * REAL_EPSILON)" + Ty_Near 0, tiny.Y, 0.000000001, "Y of (1E-8, 0) after Length = 5" + Expect.Done + End Sub + + [TestCase] + Public Sub Transposed_ExchangesTheCoordinates() + Expect.Begin + Expect.AreEqual 2, GpPointF(1, 2).Transposed.X, "X of Transposed of (1, 2)" + Expect.AreEqual 1, GpPointF(1, 2).Transposed.Y, "Y of Transposed of (1, 2)" + Expect.Done + End Sub + + ' ---- Conversion + + [TestCase] + Public Sub AsGpPoint_RoundsHalvesToEven() + Expect.Begin + Dim whole As GpPoint = GpPointF(2.5, 3.5).AsGpPoint() + Expect.AreEqual "2,4", whole.X & "," & whole.Y, "AsGpPoint of (2.5, 3.5)" + Dim other As GpPoint = GpPointF(2.6, -2.6).AsGpPoint() + Expect.AreEqual "3,-3", other.X & "," & other.Y, "AsGpPoint of (2.6, -2.6)" + Expect.Done + End Sub + + [TestCase] + Public Sub SetFrom_TakesAGpPoint() + Expect.Begin + Dim p As GpPointF + p.SetFrom GpPoint(5, 6) + Expect.AreEqual "5,6", p.X & "," & p.Y, "SetFrom GpPoint(5, 6)" + Expect.Done + End Sub + + [TestCase] + Public Sub AsPOINT_RoundsAndSetFromPOINTConverts() + Expect.Begin + Dim winPoint As GDIP_POINT = GpPointF(9.2, 1.7).AsPOINT() + Expect.AreEqual "9,2", winPoint.x & "," & winPoint.y, "AsPOINT of (9.2, 1.7)" + Dim halves As GDIP_POINT = GpPointF(2.5, 3.5).AsPOINT() + Expect.AreEqual "2,4", halves.x & "," & halves.y, "AsPOINT of (2.5, 3.5) (halves to the even number)" + Dim p As GpPointF + winPoint.x = 11: winPoint.y = 12 + p.SetFromPOINT winPoint + Expect.AreEqual "11,12", p.X & "," & p.Y, "SetFromPOINT of (11, 12)" + Expect.Done + End Sub + + ' ---- Modifying methods + + [TestCase] + Public Sub Add_ChangesThePoint() + Expect.Begin + Dim p As GpPointF = GpPointF(3, 4) + p.Add GpPointF(1, 1) + Expect.AreEqual "4,5", p.X & "," & p.Y, "the point (3, 4) after Add GpPointF(1, 1)" + Expect.Done + End Sub + + [TestCase] + Public Sub Sub_ChangesThePoint() + Expect.Begin + Dim p As GpPointF = GpPointF(4, 5) + p.Sub GpPointF(1, 1) + Expect.AreEqual "3,4", p.X & "," & p.Y, "the point (4, 5) after Sub GpPointF(1, 1)" + Expect.Done + End Sub + + [TestCase] + Public Sub Scale_MultipliesBothCoordinates() + Expect.Begin + Dim p As GpPointF = GpPointF(3, 4) + p.Scale 2 + Expect.AreEqual "6,8", p.X & "," & p.Y, "the point (3, 4) after Scale 2" + Expect.Done + End Sub + + [TestCase] + Public Sub Scale_CallFormMultipliesBothCoordinates() + Expect.Begin + Dim p As GpPointF = GpPointF(3, 4) + Call p.Scale(2) + Expect.AreEqual "6,8", p.X & "," & p.Y, "the point (3, 4) after Call p.Scale(2)" + Expect.Done + End Sub + + [TestCase] + Public Sub Scale2_MultipliesEachCoordinateByItsOwnFactor() + Expect.Begin + Dim p As GpPointF = GpPointF(6, 8) + p.Scale2 1, 3 + Expect.AreEqual "6,24", p.X & "," & p.Y, "the point (6, 8) after Scale2 1, 3" + Expect.Done + End Sub + + [TestCase] + Public Sub InvScale_And_InvScale2_Divide() + Expect.Begin + Dim p As GpPointF = GpPointF(6, 24) + p.InvScale 2 + Expect.AreEqual "3,12", p.X & "," & p.Y, "the point (6, 24) after InvScale 2" + p.InvScale2 3, 12 + Expect.AreEqual "1,1", p.X & "," & p.Y, "the point (3, 12) after InvScale2 3, 12" + Expect.Done + End Sub + + [TestCase] + Public Sub Transpose_ExchangesTheCoordinatesOfThePoint() + Expect.Begin + Dim p As GpPointF = GpPointF(1, 2) + p.Transpose + Expect.AreEqual "2,1", p.X & "," & p.Y, "the point (1, 2) after Transpose" + Expect.Done + End Sub + + [TestCase] + Public Sub Normalize_SetsTheLengthToOneAndKeepsTheDirection() + Expect.Begin + Dim p As GpPointF = GpPointF(3, 4) + p.Normalize + Ty_Near 0.6, p.X, 0.0001, "X of (3, 4) after Normalize" + Ty_Near 0.8, p.Y, 0.0001, "Y of (3, 4) after Normalize" + Expect.Done + End Sub + + [TestCase] + Public Sub Normalize_LeavesAZeroVector() + Expect.Begin + Dim p As GpPointF + p.Normalize + Expect.IsTrue p.Zero, "Zero of (0, 0) after Normalize" + Expect.Done + End Sub + + ' ---- Query methods + + [TestCase] + Public Sub SumDiffProdQuot_ReturnNewPointsAndLeaveThePoint() + Expect.Begin + Dim p As GpPointF = GpPointF(3, 4) + Dim s As GpPointF = p.Sum(GpPointF(1, 1)) + Expect.AreEqual "4,5", s.X & "," & s.Y, "Sum of (3, 4) and (1, 1)" + Dim d As GpPointF = p.Diff(GpPointF(1, 1)) + Expect.AreEqual "2,3", d.X & "," & d.Y, "Diff of (3, 4) and (1, 1)" + Dim pr As GpPointF = p.Prod(2) + Expect.AreEqual "6,8", pr.X & "," & pr.Y, "Prod(2) of (3, 4)" + Dim pr2 As GpPointF = p.Prod2(2, 3) + Expect.AreEqual "6,12", pr2.X & "," & pr2.Y, "Prod2(2, 3) of (3, 4)" + Dim q As GpPointF = p.Quot(2) + Expect.AreEqual "1.5,2", q.X & "," & q.Y, "Quot(2) of (3, 4)" + Dim q2 As GpPointF = p.Quot2(2, 4) + Expect.AreEqual "1.5,1", q2.X & "," & q2.Y, "Quot2(2, 4) of (3, 4)" + Expect.AreEqual "3,4", p.X & "," & p.Y, "the point after the query methods" + Expect.Done + End Sub + + [TestCase] + Public Sub DotProd_IsXTimesXPlusYTimesY() + Expect.Begin + Expect.AreEqual 10, GpPointF(3, 4).DotProd(GpPointF(2, 1)), "DotProd of (3, 4) and (2, 1)" + Expect.Done + End Sub + + [TestCase] + Public Sub Equals_IsExact() + Expect.Begin + Dim p As GpPointF = GpPointF(3, 4) + Expect.IsTrue p.Equals(GpPointF(3, 4)), "Equals of (3, 4) and (3, 4)" + Expect.IsFalse p.Equals(GpPointF(3, 4.0001)), "Equals of (3, 4) and (3, 4.0001)" + Expect.Done + End Sub + +End Module diff --git a/test/package-tests/GdiPlus/Sources/Ty_GpPointTests.twin b/test/package-tests/GdiPlus/Sources/Ty_GpPointTests.twin new file mode 100644 index 00000000..b967d0f6 --- /dev/null +++ b/test/package-tests/GdiPlus/Sources/Ty_GpPointTests.twin @@ -0,0 +1,172 @@ +' Ty_GpPointTests: the GpPoint type (page Types/GpPoint). +[TestFixture] +Module Ty_GpPointTests + + [TestCase] + Public Sub GpPoint_IsEightBytes() + Expect.Begin + Dim p As GpPoint + Expect.AreEqual 8, LenB(p), "LenB of a GpPoint" + Expect.Done + End Sub + + ' ---- Creating + + [TestCase] + Public Sub GpPoint_FormsGiveTheDocumentedValues() + Expect.Begin + Dim p As GpPoint = GpPoint(3, 4) + Expect.AreEqual "3,4", p.X & "," & p.Y, "GpPoint(3, 4)" + Dim blank As GpPoint = GpPoint() + Expect.AreEqual "0,0", blank.X & "," & blank.Y, "GpPoint()" + Dim declared As GpPoint + Expect.AreEqual "0,0", declared.X & "," & declared.Y, "a variable declared with Dim" + Dim fromSize As GpPoint = GpPoint(GpSize(7, 8)) + Expect.AreEqual "7,8", fromSize.X & "," & fromSize.Y, "GpPoint(GpSize(7, 8))" + Expect.Done + End Sub + + [TestCase] + Public Sub GpPoint_OfGpPointFRoundsHalvesToEven() + Expect.Begin + Dim rounded As GpPoint = GpPoint(GpPointF(2.5, 3.5)) + Expect.AreEqual "2,4", rounded.X & "," & rounded.Y, "GpPoint(GpPointF(2.5, 3.5))" + Dim negative As GpPoint = GpPoint(GpPointF(-2.5, -3.5)) + Expect.AreEqual "-2,-4", negative.X & "," & negative.Y, "GpPoint(GpPointF(-2.5, -3.5))" + Expect.Done + End Sub + + ' ---- Fields and properties + + [TestCase] + Public Sub Zero_IsTrueOnlyForBothCoordinatesZero() + Expect.Begin + Expect.IsTrue GpPoint(0, 0).Zero, "Zero of (0, 0)" + Expect.IsFalse GpPoint(0, 1).Zero, "Zero of (0, 1)" + Expect.IsFalse GpPoint(1, 0).Zero, "Zero of (1, 0)" + Expect.Done + End Sub + + [TestCase] + Public Sub Transposed_ExchangesTheCoordinatesAndLeavesThePoint() + Expect.Begin + Dim p As GpPoint = GpPoint(3, 4) + Dim t As GpPoint = p.Transposed + Expect.AreEqual "4,3", t.X & "," & t.Y, "Transposed of (3, 4)" + Expect.AreEqual "3,4", p.X & "," & p.Y, "the point after Transposed" + Expect.Done + End Sub + + ' ---- Conversion + + [TestCase] + Public Sub AsGpPointF_ReturnsTheSameCoordinates() + Expect.Begin + Dim f As GpPointF = GpPoint(3, 4).AsGpPointF() + Expect.AreEqual "3,4", f.X & "," & f.Y, "AsGpPointF of (3, 4)" + Expect.Done + End Sub + + [TestCase] + Public Sub SetFrom_RoundsAGpPointF() + Expect.Begin + Dim p As GpPoint + p.SetFrom GpPointF(2.6, -2.6) + Expect.AreEqual "3,-3", p.X & "," & p.Y, "SetFrom GpPointF(2.6, -2.6)" + p.SetFrom GpPointF(2.5, 3.5) + Expect.AreEqual "2,4", p.X & "," & p.Y, "SetFrom GpPointF(2.5, 3.5) (halves to the even number)" + Expect.Done + End Sub + + [TestCase] + Public Sub AsPOINT_And_SetFromPOINT_ConvertToAndFromThePointStructure() + Expect.Begin + Dim winPoint As GDIP_POINT = GpPoint(3, -4).AsPOINT() + Expect.AreEqual "3,-4", winPoint.x & "," & winPoint.y, "AsPOINT of (3, -4)" + Dim p As GpPoint + winPoint.x = 11: winPoint.y = 12 + p.SetFromPOINT winPoint + Expect.AreEqual "11,12", p.X & "," & p.Y, "SetFromPOINT of (11, 12)" + Expect.Done + End Sub + + ' ---- Add and Sub change the point, Sum and Diff do not + + [TestCase] + Public Sub Add_ChangesThePoint() + Expect.Begin + Dim p As GpPoint = GpPoint(3, 4) + p.Add GpPoint(10, 10) + Expect.AreEqual "13,14", p.X & "," & p.Y, "the point (3, 4) after Add GpPoint(10, 10)" + Expect.Done + End Sub + + [TestCase] + Public Sub Sub_ChangesThePoint() + Expect.Begin + Dim p As GpPoint = GpPoint(13, 14) + p.Sub GpPoint(1, 1) + Expect.AreEqual "12,13", p.X & "," & p.Y, "the point (13, 14) after Sub GpPoint(1, 1)" + Expect.Done + End Sub + + [TestCase] + Public Sub Transpose_ExchangesTheCoordinatesOfThePoint() + Expect.Begin + Dim p As GpPoint = GpPoint(13, 12) + p.Transpose + Expect.AreEqual "12,13", p.X & "," & p.Y, "the point (13, 12) after Transpose" + Expect.Done + End Sub + + [TestCase] + Public Sub SumAndDiff_ReturnNewPointsAndLeaveThePoint() + Expect.Begin + Dim p As GpPoint = GpPoint(3, 4) + Dim s As GpPoint = p.Sum(GpPoint(1, 1)) + Dim d As GpPoint = p.Diff(GpPoint(1, 1)) + Expect.AreEqual "4,5", s.X & "," & s.Y, "Sum of (3, 4) and (1, 1)" + Expect.AreEqual "2,3", d.X & "," & d.Y, "Diff of (3, 4) and (1, 1)" + Expect.AreEqual "3,4", p.X & "," & p.Y, "the point after Sum and Diff" + Expect.Done + End Sub + + ' ---- DotProd + + [TestCase] + Public Sub DotProd_IsXTimesXPlusYTimesY() + Expect.Begin + Dim p As GpPoint = GpPoint(3, 4) + Expect.AreEqual 700000, p.DotProd(GpPoint(100000, 100000)), "DotProd of (3, 4) and (100000, 100000)" + Expect.AreEqual 11, p.DotProd(GpPoint(1, 2)), "DotProd of (3, 4) and (1, 2)" + Expect.Done + End Sub + + ' The result is a LongLong, so a product above the range of a Long should be returned, not raise an error. + [TestCase] + Public Sub DotProd_ProductAboveTheRangeOfALongIsReturned() + Expect.Begin + Dim p As GpPoint = GpPoint(50000, 0) + Dim errNumber As Long + Dim product As LongLong + On Error Resume Next + Err.Clear + product = p.DotProd(GpPoint(50000, 0)) + errNumber = Err.Number + On Error GoTo 0 + Expect.AreEqual 0, errNumber, "Err.Number after DotProd of (50000, 0) and (50000, 0)" + Expect.AreEqual 2500000000^, product, "DotProd of (50000, 0) and (50000, 0)" + Expect.Done + End Sub + + [TestCase] + Public Sub Equals_ComparesBothCoordinates() + Expect.Begin + Dim p As GpPoint = GpPoint(3, 4) + Expect.IsTrue p.Equals(GpPoint(3, 4)), "Equals of (3, 4) and (3, 4)" + Expect.IsFalse p.Equals(GpPoint(3, 5)), "Equals of (3, 4) and (3, 5)" + Expect.IsFalse p.Equals(GpPoint(4, 4)), "Equals of (3, 4) and (4, 4)" + Expect.Done + End Sub + +End Module diff --git a/test/package-tests/GdiPlus/Sources/Ty_GpRectFTests.twin b/test/package-tests/GdiPlus/Sources/Ty_GpRectFTests.twin new file mode 100644 index 00000000..5df44ef9 --- /dev/null +++ b/test/package-tests/GdiPlus/Sources/Ty_GpRectFTests.twin @@ -0,0 +1,327 @@ +' Ty_GpRectFTests: the GpRectF type (page Types/GpRectF). The methods OffsetBy, OffsetByPt, InflatedBy and +' InflatedByPt are not called here: a program that names one does not link (compile/Ty_CompileTests.twin). +[TestFixture] +Module Ty_GpRectFTests + + Private Function Box(ByRef R As GpRectF) As String + Box = R.X & "," & R.Y & "," & R.Width & "," & R.Height + End Function + + [TestCase] + Public Sub GpRectF_IsSixteenBytes() + Expect.Begin + Dim r As GpRectF + Expect.AreEqual 16, LenB(r), "LenB of a GpRectF" + Expect.Done + End Sub + + ' ---- Creating + + [TestCase] + Public Sub GpRectF_FormsGiveTheDocumentedValues() + Expect.Begin + Dim r As GpRectF = GpRectF(10, 20, 30, 40) + Expect.AreEqual "10,20,30,40", Box(r), "GpRectF(10, 20, 30, 40)" + Dim blank As GpRectF = GpRectF() + Expect.AreEqual "0,0,0,0", Box(blank), "GpRectF()" + Dim declared As GpRectF + Expect.AreEqual "0,0,0,0", Box(declared), "a variable declared with Dim" + Dim xy As GpRectF = GpRectF(5, 6) + Expect.AreEqual "5,6,0,0", Box(xy), "GpRectF(5, 6)" + Dim loc As GpRectF = GpRectF(GpPointF(5, 6)) + Expect.AreEqual "5,6,0,0", Box(loc), "GpRectF(GpPointF(5, 6))" + Dim p As GpRectF = GpRectF(GpPointF(5, 6), GpSizeF(7, 8)) + Expect.AreEqual "5,6,7,8", Box(p), "GpRectF(GpPointF(5, 6), GpSizeF(7, 8))" + Dim q As GpRectF = GpRectF(GpRect(1, 2, 3, 4)) + Expect.AreEqual "1,2,3,4", Box(q), "GpRectF(GpRect(1, 2, 3, 4))" + Expect.Done + End Sub + + [TestCase] + Public Sub GpRectF_OfTheEdgesStructureTakesWidthAndHeightAsDifferences() + Expect.Begin + Dim edges As GDIP_RECTF + edges.Left = 1.5: edges.Top = 2.5: edges.Right = 11.5: edges.Bottom = 22.5 + Dim e As GpRectF = GpRectF(edges) + Expect.AreEqual "1.5,2.5,10,20", Box(e), "GpRectF of a GDIP_RECTF with the edges 1.5, 2.5, 11.5, 22.5" + Expect.Done + End Sub + + [TestCase] + Public Sub GpRectFFrom_ReadsTheLongEdgesStructure() + Expect.Begin + Dim edges As GDIP_RECT + edges.Left = 1: edges.Top = 2: edges.Right = 11: edges.Bottom = 22 + Dim e As GpRectF = GpRectFFrom(edges) + Expect.AreEqual "1,2,10,20", Box(e), "GpRectFFrom of a GDIP_RECT with the edges 1, 2, 11, 22" + Expect.Done + End Sub + + ' ---- Fields and properties + + [TestCase] + Public Sub Location_And_Size_AreIndependent() + Expect.Begin + Dim r As GpRectF = GpRectF(10, 20, 30, 40) + r.Location = GpPointF(0, 0) + Expect.AreEqual "0,0,30,40", Box(r), "the rectangle 10, 20, 30, 40 after Location = (0, 0)" + r.Size = GpSizeF(3, 4) + Expect.AreEqual "0,0,3,4", Box(r), "the rectangle after Size = 3 by 4" + Dim loc As GpPointF = r.Location + Expect.AreEqual "0,0", loc.X & "," & loc.Y, "Location of the rectangle" + Dim sz As GpSizeF = r.Size + Expect.AreEqual "3,4", sz.Width & "," & sz.Height, "Size of the rectangle" + Expect.Done + End Sub + + [TestCase] + Public Sub Edges_AreXYAndXPlusWidthYPlusHeight() + Expect.Begin + Dim r As GpRectF = GpRectF(10, 20, 30, 40) + Expect.AreEqual "10,20,40,60", r.Left & "," & r.Top & "," & r.Right & "," & r.Bottom, "Left Top Right Bottom of (10, 20, 30, 40)" + Expect.Done + End Sub + + [TestCase] + Public Sub Corners_AreThePointsOfTheRectangle() + Expect.Begin + Dim r As GpRectF = GpRectF(10, 20, 30, 40) + Dim tl As GpPointF = r.TopLeft + Dim tr As GpPointF = r.TopRight + Dim bl As GpPointF = r.BottomLeft + Dim br As GpPointF = r.BottomRight + Expect.AreEqual "10,20", tl.X & "," & tl.Y, "TopLeft of (10, 20, 30, 40)" + Expect.AreEqual "40,20", tr.X & "," & tr.Y, "TopRight of (10, 20, 30, 40)" + Expect.AreEqual "10,60", bl.X & "," & bl.Y, "BottomLeft of (10, 20, 30, 40)" + Expect.AreEqual "40,60", br.X & "," & br.Y, "BottomRight of (10, 20, 30, 40)" + Expect.Done + End Sub + + [TestCase] + Public Sub IsEmptyArea_IsTrueForWidthOrHeightAtMostEpsilon() + Expect.Begin + Expect.IsTrue GpRectF(1, 1, 0, 5).IsEmptyArea, "IsEmptyArea of a rectangle with width 0" + Expect.IsTrue GpRectF(1, 1, 5, 0).IsEmptyArea, "IsEmptyArea of a rectangle with height 0" + Expect.IsTrue GpRectF(1, 1, -2, 5).IsEmptyArea, "IsEmptyArea of a rectangle with width -2" + Expect.IsTrue GpRectF(1, 1, REAL_EPSILON, 5).IsEmptyArea, "IsEmptyArea of a rectangle with width REAL_EPSILON" + Expect.IsFalse GpRectF(1, 1, 0.001, 5).IsEmptyArea, "IsEmptyArea of a rectangle with width 0.001" + Expect.IsFalse GpRectF(10, 20, 30, 40).IsEmptyArea, "IsEmptyArea of (10, 20, 30, 40)" + Expect.Done + End Sub + + [TestCase] + Public Sub Diagonal_GetAndSetScaleWidthAndHeightKeepingTheLocation() + Expect.Begin + Dim r As GpRectF = GpRectF(7, 9, 3, 4) + Ty_Near 5, r.Diagonal, 0.0001, "Diagonal of 3 by 4" + r.Diagonal = 10 + Ty_Near 6, r.Width, 0.0001, "Width after Diagonal = 10" + Ty_Near 8, r.Height, 0.0001, "Height after Diagonal = 10" + Expect.AreEqual "7,9", r.X & "," & r.Y, "the location after Diagonal = 10" + Expect.Done + End Sub + + [TestCase] + Public Sub Diagonal_SetOfARectangleShorterThanTwiceEpsilonChangesNothing() + Expect.Begin + Dim r As GpRectF = GpRectF(1, 1, 0.00000001!, 0) + r.Diagonal = 10 + Ty_Near 0.00000001, r.Width, 0.000000001, "Width of 1E-8 by 0 after Diagonal = 10" + Ty_Near 0, r.Height, 0.000000001, "Height of 1E-8 by 0 after Diagonal = 10" + Expect.Done + End Sub + + ' ---- Conversion + + [TestCase] + Public Sub AsGpRect_RoundsAllFourValuesHalvesToEven() + Expect.Begin + Dim whole As GpRect = GpRectF(1.5, 2.5, 3.5, 4.5).AsGpRect() + Expect.AreEqual "2,2,4,4", whole.X & "," & whole.Y & "," & whole.Width & "," & whole.Height, "AsGpRect of (1.5, 2.5, 3.5, 4.5)" + Expect.Done + End Sub + + [TestCase] + Public Sub SetFrom_TakesAGpRect() + Expect.Begin + Dim r As GpRectF + r.SetFrom GpRect(1, 2, 3, 4) + Expect.AreEqual "1,2,3,4", Box(r), "SetFrom GpRect(1, 2, 3, 4)" + Expect.Done + End Sub + + [TestCase] + Public Sub AsRECTF_And_ToRECTF_ReturnTheEdges() + Expect.Begin + Dim r As GpRectF = GpRectF(1.5, 2.5, 3.5, 4.5) + Dim edgesF As GDIP_RECTF = r.AsRECTF() + Expect.AreEqual "1.5,2.5,5,7", edgesF.Left & "," & edgesF.Top & "," & edgesF.Right & "," & edgesF.Bottom, "AsRECTF of (1.5, 2.5, 3.5, 4.5)" + Dim written As GDIP_RECTF + r.ToRECTF written + Expect.AreEqual "1.5,2.5,5,7", written.Left & "," & written.Top & "," & written.Right & "," & written.Bottom, "ToRECTF of (1.5, 2.5, 3.5, 4.5)" + Expect.Done + End Sub + + [TestCase] + Public Sub AsRECT_RoundsEachEdge() + Expect.Begin + Dim r As GpRectF = GpRectF(1.5, 2.5, 3.5, 4.5) + Dim edges As GDIP_RECT = r.AsRECT() + Expect.AreEqual "2,2,5,7", edges.Left & "," & edges.Top & "," & edges.Right & "," & edges.Bottom, "AsRECT of (1.5, 2.5, 3.5, 4.5): edges 1.5, 2.5, 5, 7 rounded" + Expect.Done + End Sub + + [TestCase] + Public Sub SetFromRECT_And_SetFromRECTF_SetPositionAndSize() + Expect.Begin + Dim r As GpRectF + Dim winRect As GDIP_RECT + winRect.Left = 1: winRect.Top = 2: winRect.Right = 11: winRect.Bottom = 22 + r.SetFromRECT winRect + Expect.AreEqual "1,2,10,20", Box(r), "SetFromRECT of the edges 1, 2, 11, 22" + Dim edgesF As GDIP_RECTF + edgesF.Left = 1.5: edgesF.Top = 2.5: edgesF.Right = 11.5: edgesF.Bottom = 22.5 + r.SetFromRECTF edgesF + Expect.AreEqual "1.5,2.5,10,20", Box(r), "SetFromRECTF of the edges 1.5, 2.5, 11.5, 22.5" + Expect.Done + End Sub + + ' ---- Modifying methods + + [TestCase] + Public Sub Offset_And_OffsetPt_MoveTheRectangle() + Expect.Begin + Dim r As GpRectF = GpRectF(10, 20, 30, 40) + r.Offset 1, 2 + Expect.AreEqual "11,22,30,40", Box(r), "(10, 20, 30, 40) after Offset 1, 2" + r.OffsetPt GpPointF(-1, -2) + Expect.AreEqual "10,20,30,40", Box(r), "the rectangle after OffsetPt (-1, -2)" + Expect.Done + End Sub + + [TestCase] + Public Sub Inflate_And_InflatePt_MoveTheEdgesApart() + Expect.Begin + Dim r As GpRectF = GpRectF(10, 20, 30, 40) + r.Inflate 5, 6 + Expect.AreEqual "5,14,40,52", Box(r), "(10, 20, 30, 40) after Inflate 5, 6" + r.InflatePt GpPointF(-5, -6) + Expect.AreEqual "10,20,30,40", Box(r), "the rectangle after InflatePt (-5, -6)" + Expect.Done + End Sub + + [TestCase] + Public Sub Intersect_SetsTheIntersection() + Expect.Begin + Dim a As GpRectF = GpRectF(0, 0, 10, 10) + Dim overlaps As Boolean = a.Intersect(GpRectF(5, 5, 10, 10)) + Expect.IsTrue overlaps, "Intersect of (0, 0, 10, 10) and (5, 5, 10, 10)" + Expect.AreEqual "5,5,5,5", Box(a), "(0, 0, 10, 10) after Intersect with (5, 5, 10, 10)" + Expect.Done + End Sub + + [TestCase] + Public Sub Intersect_OfRectanglesThatDoNotOverlapGivesNegativeSize() + Expect.Begin + Dim b As GpRectF = GpRectF(5, 5, 10, 10) + Dim overlaps As Boolean = b.Intersect(GpRectF(100, 100, 5, 5)) + Expect.IsFalse overlaps, "Intersect of (5, 5, 10, 10) and (100, 100, 5, 5)" + Expect.AreEqual "-85,-85", b.Width & "," & b.Height, "Width and Height after the intersection of rectangles that do not overlap" + Expect.Done + End Sub + + [TestCase] + Public Sub Unite_SetsTheBoundingRectangle() + Expect.Begin + Dim u As GpRectF = GpRectF(0, 0, 10, 10) + Dim notEmpty As Boolean = u.Unite(GpRectF(5, 5, 10, 10)) + Expect.IsTrue notEmpty, "Unite of (0, 0, 10, 10) and (5, 5, 10, 10)" + Expect.AreEqual "0,0,15,15", Box(u), "(0, 0, 10, 10) after Unite with (5, 5, 10, 10)" + Expect.Done + End Sub + + [TestCase] + Public Sub Unite_OfTwoEmptyRectanglesAtTheSameLocationIsFalse() + Expect.Begin + Expect.IsFalse GpRectF(1, 1).Unite(GpRectF(1, 1)), "Unite of two empty rectangles at (1, 1)" + Dim apart As GpRectF = GpRectF(1, 1, 0, 0) + Dim notEmpty As Boolean = apart.Unite(GpRectF(5, 5, 0, 0)) + Expect.IsTrue notEmpty, "Unite of empty rectangles at (1, 1) and (5, 5)" + Expect.AreEqual "1,1,4,4", Box(apart), "the empty rectangle at (1, 1) after Unite with the empty rectangle at (5, 5)" + Expect.Done + End Sub + + ' ---- Query methods + + [TestCase] + Public Sub Equals_IsExact() + Expect.Begin + Dim r As GpRectF = GpRectF(10, 20, 30, 40) + Expect.IsTrue r.Equals(GpRectF(10, 20, 30, 40)), "Equals of equal rectangles" + Expect.IsFalse r.Equals(GpRectF(10, 20, 30, 40.001)), "Equals of rectangles whose heights differ by 0.001" + Expect.Done + End Sub + + [TestCase] + Public Sub Contains2_And_ContainsPt_IncludeLeftAndTopEdgesButNotRightAndBottom() + Expect.Begin + Dim r As GpRectF = GpRectF(10, 20, 30, 40) + Expect.IsTrue r.Contains2(10, 20), "Contains2 of the top left corner" + Expect.IsFalse r.Contains2(40, 60), "Contains2 of the bottom right corner (X + Width, Y + Height)" + Expect.IsFalse r.Contains2(40, 30), "Contains2 of a point on the right edge" + Expect.IsFalse r.Contains2(20, 60), "Contains2 of a point on the bottom edge" + Expect.IsTrue r.ContainsPt(GpPointF(39.5, 59.5)), "ContainsPt of (39.5, 59.5)" + Expect.IsFalse r.ContainsPt(GpPointF(9.5, 20)), "ContainsPt of (9.5, 20)" + Expect.Done + End Sub + + [TestCase] + Public Sub Contains_CountsSharingAnEdgeAsWithin() + Expect.Begin + Dim r As GpRectF = GpRectF(10, 20, 30, 40) + Expect.IsTrue r.Contains(GpRectF(11, 21, 5, 5)), "Contains of a rectangle inside" + Expect.IsTrue r.Contains(GpRectF(10, 20, 30, 40)), "Contains of the same rectangle" + Expect.IsTrue r.Contains(GpRectF(35, 55, 5, 5)), "Contains of a rectangle that shares the right and bottom edges" + Expect.IsFalse r.Contains(GpRectF(5, 5, 30, 40)), "Contains of a rectangle that sticks out" + Expect.Done + End Sub + + [TestCase] + Public Sub IntersectsWith_RectanglesThatOnlyTouchDoNotOverlap() + Expect.Begin + Dim r As GpRectF = GpRectF(10, 20, 30, 40) + Expect.IsFalse r.IntersectsWith(GpRectF(40, 60, 5, 5)), "IntersectsWith of a rectangle at the bottom right corner" + Expect.IsFalse r.IntersectsWith(GpRectF(40, 20, 5, 5)), "IntersectsWith of a rectangle that touches the right edge" + Expect.IsTrue r.IntersectsWith(GpRectF(30, 40, 30, 40)), "IntersectsWith of an overlapping rectangle" + Expect.Done + End Sub + + [TestCase] + Public Sub IntersectedWith_And_UnitedWith_ReturnNewRectangles() + Expect.Begin + Dim r As GpRectF = GpRectF(10, 20, 30, 40) + Dim i As GpRectF = r.IntersectedWith(GpRectF(30, 40, 30, 40)) + Expect.AreEqual "30,40,10,20", Box(i), "IntersectedWith of (10, 20, 30, 40) and (30, 40, 30, 40)" + Dim u As GpRectF = r.UnitedWith(GpRectF(30, 40, 30, 40)) + Expect.AreEqual "10,20,50,60", Box(u), "UnitedWith of (10, 20, 30, 40) and (30, 40, 30, 40)" + Expect.AreEqual "10,20,30,40", Box(r), "the rectangle after IntersectedWith and UnitedWith" + Dim apart As GpRectF = r.IntersectedWith(GpRectF(100, 100, 5, 5)) + Expect.IsTrue apart.Width < 0 Or apart.Height < 0, "IntersectedWith of rectangles that do not overlap has a negative width or height" + Expect.Done + End Sub + + ' ---- Free-standing functions + + [TestCase] + Public Sub Intersection_And_Union_SetTheResult() + Expect.Begin + Dim c As GpRectF + Expect.IsTrue Intersection(c, GpRectF(0, 0, 10, 10), GpRectF(5, 5, 10, 10)), "Intersection of (0, 0, 10, 10) and (5, 5, 10, 10)" + Expect.AreEqual "5,5,5,5", Box(c), "the result of Intersection" + Expect.IsTrue Union(c, GpRectF(0, 0, 10, 10), GpRectF(5, 5, 10, 10)), "Union of (0, 0, 10, 10) and (5, 5, 10, 10)" + Expect.AreEqual "0,0,15,15", Box(c), "the result of Union" + Expect.IsFalse Intersection(c, GpRectF(0, 0, 10, 10), GpRectF(50, 50, 10, 10)), "Intersection of rectangles that do not overlap" + Expect.Done + End Sub + +End Module diff --git a/test/package-tests/GdiPlus/Sources/Ty_GpRectTests.twin b/test/package-tests/GdiPlus/Sources/Ty_GpRectTests.twin new file mode 100644 index 00000000..adf5b03b --- /dev/null +++ b/test/package-tests/GdiPlus/Sources/Ty_GpRectTests.twin @@ -0,0 +1,307 @@ +' Ty_GpRectTests: the GpRect type (page Types/GpRect). The methods OffsetBy, OffsetByPt, InflatedBy and +' InflatedByPt are not called here: a program that names one does not link (compile/Ty_CompileTests.twin). +[TestFixture] +Module Ty_GpRectTests + + Private Function Box(ByRef R As GpRect) As String + Box = R.X & "," & R.Y & "," & R.Width & "," & R.Height + End Function + + [TestCase] + Public Sub GpRect_IsSixteenBytes() + Expect.Begin + Dim r As GpRect + Expect.AreEqual 16, LenB(r), "LenB of a GpRect" + Expect.Done + End Sub + + ' ---- Creating + + [TestCase] + Public Sub GpRect_FormsGiveTheDocumentedValues() + Expect.Begin + Dim r As GpRect = GpRect(10, 20, 30, 40) + Expect.AreEqual "10,20,30,40", Box(r), "GpRect(10, 20, 30, 40)" + Dim blank As GpRect = GpRect() + Expect.AreEqual "0,0,0,0", Box(blank), "GpRect()" + Dim declared As GpRect + Expect.AreEqual "0,0,0,0", Box(declared), "a variable declared with Dim" + Dim xy As GpRect = GpRect(5, 6) + Expect.AreEqual "5,6,0,0", Box(xy), "GpRect(5, 6)" + Dim loc As GpRect = GpRect(GpPoint(5, 6)) + Expect.AreEqual "5,6,0,0", Box(loc), "GpRect(GpPoint(5, 6))" + Dim p As GpRect = GpRect(GpPoint(5, 6), GpSize(7, 8)) + Expect.AreEqual "5,6,7,8", Box(p), "GpRect(GpPoint(5, 6), GpSize(7, 8))" + Expect.Done + End Sub + + [TestCase] + Public Sub GpRect_OfGpRectFRoundsHalvesToEven() + Expect.Begin + Dim q As GpRect = GpRect(GpRectF(1.5, 2.5, 3.5, 4.5)) + Expect.AreEqual "2,2,4,4", Box(q), "GpRect(GpRectF(1.5, 2.5, 3.5, 4.5))" + Expect.Done + End Sub + + [TestCase] + Public Sub GpRect_OfTheEdgesStructureTakesWidthAndHeightAsDifferences() + Expect.Begin + Dim edges As GDIP_RECT + edges.Left = 1: edges.Top = 2: edges.Right = 11: edges.Bottom = 22 + Dim e As GpRect = GpRect(edges) + Expect.AreEqual "1,2,10,20", Box(e), "GpRect of a GDIP_RECT with the edges 1, 2, 11, 22" + Expect.Done + End Sub + + [TestCase] + Public Sub GpRectFrom_ReadsTheSingleEdgesStructure() + Expect.Begin + Dim edges As GDIP_RECTF + edges.Left = 1.5: edges.Top = 2.5: edges.Right = 11.5: edges.Bottom = 22.5 + Dim e As GpRect = GpRectFrom(edges) + Expect.AreEqual "2,2,10,20", Box(e), "GpRectFrom of a GDIP_RECTF with the edges 1.5, 2.5, 11.5, 22.5" + Expect.Done + End Sub + + ' ---- Fields and properties + + [TestCase] + Public Sub Location_And_Size_AreIndependent() + Expect.Begin + Dim r As GpRect = GpRect(10, 20, 30, 40) + r.Location = GpPoint(0, 0) + Expect.AreEqual "0,0,30,40", Box(r), "the rectangle 10, 20, 30, 40 after Location = (0, 0)" + r.Size = GpSize(3, 4) + Expect.AreEqual "0,0,3,4", Box(r), "the rectangle after Size = 3 by 4" + Dim loc As GpPoint = r.Location + Expect.AreEqual "0,0", loc.X & "," & loc.Y, "Location of the rectangle" + Dim sz As GpSize = r.Size + Expect.AreEqual "3,4", sz.Width & "," & sz.Height, "Size of the rectangle" + Expect.Done + End Sub + + [TestCase] + Public Sub Edges_AreXYAndXPlusWidthYPlusHeight() + Expect.Begin + Dim r As GpRect = GpRect(10, 20, 30, 40) + Expect.AreEqual "10,20,40,60", r.Left & "," & r.Top & "," & r.Right & "," & r.Bottom, "Left Top Right Bottom of (10, 20, 30, 40)" + Expect.Done + End Sub + + [TestCase] + Public Sub Corners_AreThePointsOfTheRectangle() + Expect.Begin + Dim r As GpRect = GpRect(10, 20, 30, 40) + Dim tl As GpPoint = r.TopLeft + Dim tr As GpPoint = r.TopRight + Dim bl As GpPoint = r.BottomLeft + Dim br As GpPoint = r.BottomRight + Expect.AreEqual "10,20", tl.X & "," & tl.Y, "TopLeft of (10, 20, 30, 40)" + Expect.AreEqual "40,20", tr.X & "," & tr.Y, "TopRight of (10, 20, 30, 40)" + Expect.AreEqual "10,60", bl.X & "," & bl.Y, "BottomLeft of (10, 20, 30, 40)" + Expect.AreEqual "40,60", br.X & "," & br.Y, "BottomRight of (10, 20, 30, 40)" + Expect.Done + End Sub + + [TestCase] + Public Sub IsEmptyArea_IsTrueForZeroOrNegativeWidthOrHeight() + Expect.Begin + Expect.IsTrue GpRect(1, 1, 0, 5).IsEmptyArea, "IsEmptyArea of a rectangle with width 0" + Expect.IsTrue GpRect(1, 1, 5, 0).IsEmptyArea, "IsEmptyArea of a rectangle with height 0" + Expect.IsTrue GpRect(1, 1, -2, 5).IsEmptyArea, "IsEmptyArea of a rectangle with width -2" + Expect.IsFalse GpRect(10, 20, 30, 40).IsEmptyArea, "IsEmptyArea of (10, 20, 30, 40)" + Expect.Done + End Sub + + ' ---- Conversion + + [TestCase] + Public Sub AsGpRectF_ReturnsTheSameRectangle() + Expect.Begin + Dim f As GpRectF = GpRect(10, 20, 30, 40).AsGpRectF() + Expect.AreEqual "40,60", f.Right & "," & f.Bottom, "Right and Bottom of AsGpRectF of (10, 20, 30, 40)" + Expect.AreEqual "10,20", f.X & "," & f.Y, "X and Y of AsGpRectF of (10, 20, 30, 40)" + Expect.Done + End Sub + + [TestCase] + Public Sub SetFrom_RoundsAGpRectF() + Expect.Begin + Dim r As GpRect + r.SetFrom GpRectF(1.5, 2.5, 3.5, 4.5) + Expect.AreEqual "2,2,4,4", Box(r), "SetFrom GpRectF(1.5, 2.5, 3.5, 4.5)" + Expect.Done + End Sub + + [TestCase] + Public Sub AsRECT_AsRECTF_ToRECT_ReturnTheEdges() + Expect.Begin + Dim r As GpRect = GpRect(10, 20, 30, 40) + Dim edges As GDIP_RECT = r.AsRECT() + Expect.AreEqual "10,20,40,60", edges.Left & "," & edges.Top & "," & edges.Right & "," & edges.Bottom, "AsRECT of (10, 20, 30, 40)" + Dim edgesF As GDIP_RECTF = r.AsRECTF() + Expect.AreEqual "10,20,40,60", edgesF.Left & "," & edgesF.Top & "," & edgesF.Right & "," & edgesF.Bottom, "AsRECTF of (10, 20, 30, 40)" + Dim written As GDIP_RECT + r.ToRECT written + Expect.AreEqual "10,20,40,60", written.Left & "," & written.Top & "," & written.Right & "," & written.Bottom, "ToRECT of (10, 20, 30, 40)" + Expect.Done + End Sub + + [TestCase] + Public Sub SetFromRECT_And_SetFromRECTF_SetPositionAndSize() + Expect.Begin + Dim r As GpRect + Dim edges As GDIP_RECT + edges.Left = 1: edges.Top = 2: edges.Right = 11: edges.Bottom = 22 + r.SetFromRECT edges + Expect.AreEqual "1,2,10,20", Box(r), "SetFromRECT of the edges 1, 2, 11, 22" + Dim edgesF As GDIP_RECTF + edgesF.Left = 1.5: edgesF.Top = 2.5: edgesF.Right = 11.5: edgesF.Bottom = 22.5 + r.SetFromRECTF edgesF + Expect.AreEqual "2,2,10,20", Box(r), "SetFromRECTF of the edges 1.5, 2.5, 11.5, 22.5 (rounded)" + Expect.Done + End Sub + + ' ---- Modifying methods + + [TestCase] + Public Sub Offset_And_OffsetPt_MoveTheRectangle() + Expect.Begin + Dim r As GpRect = GpRect(10, 20, 30, 40) + r.Offset 1, 2 + Expect.AreEqual "11,22,30,40", Box(r), "(10, 20, 30, 40) after Offset 1, 2" + r.OffsetPt GpPoint(-1, -2) + Expect.AreEqual "10,20,30,40", Box(r), "the rectangle after OffsetPt (-1, -2)" + Expect.Done + End Sub + + [TestCase] + Public Sub Inflate_And_InflatePt_MoveTheEdgesApart() + Expect.Begin + Dim r As GpRect = GpRect(10, 20, 30, 40) + r.Inflate 5, 6 + Expect.AreEqual "5,14,40,52", Box(r), "(10, 20, 30, 40) after Inflate 5, 6" + r.InflatePt GpPoint(-5, -6) + Expect.AreEqual "10,20,30,40", Box(r), "the rectangle after InflatePt (-5, -6)" + Expect.Done + End Sub + + [TestCase] + Public Sub Intersect_SetsTheIntersection() + Expect.Begin + Dim a As GpRect = GpRect(0, 0, 10, 10) + Dim overlaps As Boolean = a.Intersect(GpRect(5, 5, 10, 10)) + Expect.IsTrue overlaps, "Intersect of (0, 0, 10, 10) and (5, 5, 10, 10)" + Expect.AreEqual "5,5,5,5", Box(a), "(0, 0, 10, 10) after Intersect with (5, 5, 10, 10)" + Expect.Done + End Sub + + [TestCase] + Public Sub Intersect_OfRectanglesThatDoNotOverlapGivesNegativeSize() + Expect.Begin + Dim b As GpRect = GpRect(5, 5, 10, 10) + Dim overlaps As Boolean = b.Intersect(GpRect(100, 100, 5, 5)) + Expect.IsFalse overlaps, "Intersect of (5, 5, 10, 10) and (100, 100, 5, 5)" + Expect.AreEqual "-85,-85", b.Width & "," & b.Height, "Width and Height after the intersection of rectangles that do not overlap" + Expect.Done + End Sub + + [TestCase] + Public Sub Unite_SetsTheBoundingRectangle() + Expect.Begin + Dim u As GpRect = GpRect(0, 0, 10, 10) + Dim notEmpty As Boolean = u.Unite(GpRect(5, 5, 10, 10)) + Expect.IsTrue notEmpty, "Unite of (0, 0, 10, 10) and (5, 5, 10, 10)" + Expect.AreEqual "0,0,15,15", Box(u), "(0, 0, 10, 10) after Unite with (5, 5, 10, 10)" + Expect.Done + End Sub + + [TestCase] + Public Sub Unite_OfTwoEmptyRectanglesAtTheSameLocationIsFalse() + Expect.Begin + Expect.IsFalse GpRect(1, 1).Unite(GpRect(1, 1)), "Unite of two empty rectangles at (1, 1)" + Dim apart As GpRect = GpRect(1, 1, 0, 0) + Dim notEmpty As Boolean = apart.Unite(GpRect(5, 5, 0, 0)) + Expect.IsTrue notEmpty, "Unite of empty rectangles at (1, 1) and (5, 5)" + Expect.AreEqual "1,1,4,4", Box(apart), "the empty rectangle at (1, 1) after Unite with the empty rectangle at (5, 5)" + Dim withReal As GpRect = GpRect(1, 1, 0, 0) + Expect.IsTrue withReal.Unite(GpRect(10, 10, 5, 5)), "Unite of an empty rectangle at (1, 1) and (10, 10, 5, 5)" + Expect.AreEqual "1,1,14,14", Box(withReal), "the empty rectangle after Unite with (10, 10, 5, 5): it counts as the point (1, 1)" + Expect.Done + End Sub + + ' ---- Query methods + + [TestCase] + Public Sub Equals_ComparesPositionAndSize() + Expect.Begin + Dim r As GpRect = GpRect(10, 20, 30, 40) + Expect.IsTrue r.Equals(GpRect(10, 20, 30, 40)), "Equals of equal rectangles" + Expect.IsFalse r.Equals(GpRect(10, 20, 30, 41)), "Equals of rectangles of different height" + Expect.IsFalse r.Equals(GpRect(11, 20, 30, 40)), "Equals of rectangles at different X" + Expect.Done + End Sub + + [TestCase] + Public Sub Contains2_And_ContainsPt_IncludeLeftAndTopEdgesButNotRightAndBottom() + Expect.Begin + Dim r As GpRect = GpRect(10, 20, 30, 40) + Expect.IsTrue r.Contains2(10, 20), "Contains2 of the top left corner" + Expect.IsFalse r.Contains2(40, 60), "Contains2 of the bottom right corner (X + Width, Y + Height)" + Expect.IsFalse r.Contains2(40, 30), "Contains2 of a point on the right edge" + Expect.IsFalse r.Contains2(20, 60), "Contains2 of a point on the bottom edge" + Expect.IsTrue r.ContainsPt(GpPoint(39, 59)), "ContainsPt of (39, 59)" + Expect.IsFalse r.ContainsPt(GpPoint(9, 20)), "ContainsPt of (9, 20)" + Expect.Done + End Sub + + [TestCase] + Public Sub Contains_CountsSharingAnEdgeAsWithin() + Expect.Begin + Dim r As GpRect = GpRect(10, 20, 30, 40) + Expect.IsTrue r.Contains(GpRect(11, 21, 5, 5)), "Contains of a rectangle inside" + Expect.IsTrue r.Contains(GpRect(10, 20, 30, 40)), "Contains of the same rectangle" + Expect.IsTrue r.Contains(GpRect(35, 55, 5, 5)), "Contains of a rectangle that shares the right and bottom edges" + Expect.IsFalse r.Contains(GpRect(5, 5, 30, 40)), "Contains of a rectangle that sticks out" + Expect.Done + End Sub + + [TestCase] + Public Sub IntersectsWith_RectanglesThatOnlyTouchDoNotOverlap() + Expect.Begin + Dim r As GpRect = GpRect(10, 20, 30, 40) + Expect.IsFalse r.IntersectsWith(GpRect(40, 60, 5, 5)), "IntersectsWith of a rectangle at the bottom right corner" + Expect.IsFalse r.IntersectsWith(GpRect(40, 20, 5, 5)), "IntersectsWith of a rectangle that touches the right edge" + Expect.IsTrue r.IntersectsWith(GpRect(30, 40, 30, 40)), "IntersectsWith of an overlapping rectangle" + Expect.Done + End Sub + + [TestCase] + Public Sub IntersectedWith_And_UnitedWith_ReturnNewRectangles() + Expect.Begin + Dim r As GpRect = GpRect(10, 20, 30, 40) + Dim i As GpRect = r.IntersectedWith(GpRect(30, 40, 30, 40)) + Expect.AreEqual "30,40,10,20", Box(i), "IntersectedWith of (10, 20, 30, 40) and (30, 40, 30, 40)" + Dim u As GpRect = r.UnitedWith(GpRect(30, 40, 30, 40)) + Expect.AreEqual "10,20,50,60", Box(u), "UnitedWith of (10, 20, 30, 40) and (30, 40, 30, 40)" + Expect.AreEqual "10,20,30,40", Box(r), "the rectangle after IntersectedWith and UnitedWith" + Dim apart As GpRect = r.IntersectedWith(GpRect(100, 100, 5, 5)) + Expect.IsTrue apart.Width < 0 Or apart.Height < 0, "IntersectedWith of rectangles that do not overlap has a negative width or height" + Expect.Done + End Sub + + ' ---- Free-standing functions + + [TestCase] + Public Sub Intersection_And_Union_SetTheResult() + Expect.Begin + Dim c As GpRect + Expect.IsTrue Intersection(c, GpRect(0, 0, 10, 10), GpRect(5, 5, 10, 10)), "Intersection of (0, 0, 10, 10) and (5, 5, 10, 10)" + Expect.AreEqual "5,5,5,5", Box(c), "the result of Intersection" + Expect.IsTrue Union(c, GpRect(0, 0, 10, 10), GpRect(5, 5, 10, 10)), "Union of (0, 0, 10, 10) and (5, 5, 10, 10)" + Expect.AreEqual "0,0,15,15", Box(c), "the result of Union" + Expect.IsFalse Intersection(c, GpRect(0, 0, 10, 10), GpRect(50, 50, 10, 10)), "Intersection of rectangles that do not overlap" + Expect.Done + End Sub + +End Module diff --git a/test/package-tests/GdiPlus/Sources/Ty_GpSizeFTests.twin b/test/package-tests/GdiPlus/Sources/Ty_GpSizeFTests.twin new file mode 100644 index 00000000..fef5333d --- /dev/null +++ b/test/package-tests/GdiPlus/Sources/Ty_GpSizeFTests.twin @@ -0,0 +1,196 @@ +' Ty_GpSizeFTests: the GpSizeF type (page Types/GpSizeF). +[TestFixture] +Module Ty_GpSizeFTests + + [TestCase] + Public Sub GpSizeF_IsEightBytes() + Expect.Begin + Dim s As GpSizeF + Expect.AreEqual 8, LenB(s), "LenB of a GpSizeF" + Expect.Done + End Sub + + ' ---- Creating + + [TestCase] + Public Sub GpSizeF_FormsGiveTheDocumentedValues() + Expect.Begin + Dim s As GpSizeF = GpSizeF(3, 4) + Expect.AreEqual "3,4", s.Width & "," & s.Height, "GpSizeF(3, 4)" + Expect.IsTrue GpSizeF().Empty, "Empty of GpSizeF()" + Dim declared As GpSizeF + Expect.IsTrue declared.Empty, "Empty of a variable declared with Dim" + Dim fromInt As GpSizeF = GpSizeF(GpSize(7, 8)) + Expect.AreEqual "7,8", fromInt.Width & "," & fromInt.Height, "GpSizeF(GpSize(7, 8))" + Expect.Done + End Sub + + [TestCase] + Public Sub GpSizeFFrom_ReadsTheSizeStructure() + Expect.Begin + Dim winSize As GDIP_SIZE + winSize.cx = 9: winSize.cy = 10 + Dim s As GpSizeF = GpSizeFFrom(winSize) + Expect.AreEqual "9,10", s.Width & "," & s.Height, "GpSizeFFrom of a GDIP_SIZE of 9 by 10" + Expect.Done + End Sub + + ' ---- Fields and properties + + [TestCase] + Public Sub Empty_IsTrueOnlyForExactlyZero() + Expect.Begin + Expect.IsTrue GpSizeF(0, 0).Empty, "Empty of 0 by 0" + Expect.IsFalse GpSizeF(0, 0.001).Empty, "Empty of 0 by 0.001" + Expect.Done + End Sub + + [TestCase] + Public Sub Diagonal_GetAndSetScaleBothDimensions() + Expect.Begin + Dim s As GpSizeF = GpSizeF(3, 4) + Ty_Near 5, s.Diagonal, 0.0001, "Diagonal of 3 by 4" + s.Diagonal = 10 + Ty_Near 6, s.Width, 0.0001, "Width of 3 by 4 after Diagonal = 10" + Ty_Near 8, s.Height, 0.0001, "Height of 3 by 4 after Diagonal = 10" + Expect.Done + End Sub + + [TestCase] + Public Sub Diagonal_SetOfASizeShorterThanTwiceEpsilonChangesNothing() + Expect.Begin + Dim s As GpSizeF = GpSizeF(0.00000001!, 0) + s.Diagonal = 10 + Ty_Near 0.00000001, s.Width, 0.000000001, "Width of 1E-8 by 0 after Diagonal = 10" + Ty_Near 0, s.Height, 0.000000001, "Height of 1E-8 by 0 after Diagonal = 10" + Expect.Done + End Sub + + ' ---- Conversion + + [TestCase] + Public Sub AsGpSize_RoundsHalvesToEven() + Expect.Begin + Dim whole As GpSize = GpSizeF(2.5, 3.5).AsGpSize() + Expect.AreEqual "2,4", whole.Width & "," & whole.Height, "AsGpSize of 2.5 by 3.5" + Expect.Done + End Sub + + [TestCase] + Public Sub SetFrom_TakesAGpSize() + Expect.Begin + Dim s As GpSizeF + s.SetFrom GpSize(5, 6) + Expect.AreEqual "5,6", s.Width & "," & s.Height, "SetFrom GpSize(5, 6)" + Expect.Done + End Sub + + [TestCase] + Public Sub AsSIZE_RoundsAndSetFromSIZEConverts() + Expect.Begin + Dim s As GpSizeF = GpSizeF(2.5, 3.5) + Dim winSize As GDIP_SIZE = s.AsSIZE() + Expect.AreEqual "2,4", winSize.cx & "," & winSize.cy, "AsSIZE of 2.5 by 3.5 (halves to the even number)" + winSize.cx = 9: winSize.cy = 10 + s.SetFromSIZE winSize + Expect.AreEqual "9,10", s.Width & "," & s.Height, "SetFromSIZE of 9 by 10" + Expect.Done + End Sub + + [TestCase] + Public Sub AsGpRectF_AsGpRect_AsRECT_ReturnARectangleAtTheOrigin() + Expect.Begin + Dim s As GpSizeF = GpSizeF(2.5, 3.5) + Dim rf As GpRectF = s.AsGpRectF() + Expect.AreEqual "0,0,2.5,3.5", rf.X & "," & rf.Y & "," & rf.Width & "," & rf.Height, "AsGpRectF of 2.5 by 3.5" + Dim r As GpRect = s.AsGpRect() + Expect.AreEqual "0,0,2,4", r.X & "," & r.Y & "," & r.Width & "," & r.Height, "AsGpRect of 2.5 by 3.5" + Dim edges As GDIP_RECT = s.AsRECT() + Expect.AreEqual "0,0,2,4", edges.Left & "," & edges.Top & "," & edges.Right & "," & edges.Bottom, "AsRECT of 2.5 by 3.5" + Expect.Done + End Sub + + ' ---- Modifying methods + + [TestCase] + Public Sub Add_ChangesTheSize() + Expect.Begin + Dim s As GpSizeF = GpSizeF(3, 4) + s.Add GpSizeF(1, 1) + Expect.AreEqual "4,5", s.Width & "," & s.Height, "the size 3 by 4 after Add GpSizeF(1, 1)" + Expect.Done + End Sub + + [TestCase] + Public Sub Sub_ChangesTheSize() + Expect.Begin + Dim s As GpSizeF = GpSizeF(4, 5) + s.Sub GpSizeF(0, 2) + Expect.AreEqual "4,3", s.Width & "," & s.Height, "the size 4 by 5 after Sub GpSizeF(0, 2)" + Expect.Done + End Sub + + [TestCase] + Public Sub Scale_MultipliesBothDimensions() + Expect.Begin + Dim s As GpSizeF = GpSizeF(4, 3) + s.Scale 2 + Expect.AreEqual "8,6", s.Width & "," & s.Height, "the size 4 by 3 after Scale 2" + Expect.Done + End Sub + + [TestCase] + Public Sub Scale_CallFormMultipliesBothDimensions() + Expect.Begin + Dim s As GpSizeF = GpSizeF(4, 3) + Call s.Scale(2) + Expect.AreEqual "8,6", s.Width & "," & s.Height, "the size 4 by 3 after Call s.Scale(2)" + Expect.Done + End Sub + + [TestCase] + Public Sub Scale2_MultipliesEachDimensionByItsOwnFactor() + Expect.Begin + Dim s As GpSizeF = GpSizeF(8, 6) + s.Scale2 1, 3 + Expect.AreEqual "8,18", s.Width & "," & s.Height, "the size 8 by 6 after Scale2 1, 3" + Expect.Done + End Sub + + [TestCase] + Public Sub InvScale_And_InvScale2_Divide() + Expect.Begin + Dim s As GpSizeF = GpSizeF(8, 18) + s.InvScale 2 + Expect.AreEqual "4,9", s.Width & "," & s.Height, "the size 8 by 18 after InvScale 2" + s.InvScale2 2, 9 + Expect.AreEqual "2,1", s.Width & "," & s.Height, "the size 4 by 9 after InvScale2 2, 9" + Expect.Done + End Sub + + ' ---- Query methods + + [TestCase] + Public Sub SumDiffProdQuot_ReturnNewSizesAndLeaveTheSize() + Expect.Begin + Dim s As GpSizeF = GpSizeF(3, 4) + Expect.AreEqual 4, s.Sum(GpSizeF(1, 1)).Width, "Width of Sum of 3 by 4 and 1 by 1" + Expect.AreEqual 3, s.Diff(GpSizeF(1, 1)).Height, "Height of Diff of 3 by 4 and 1 by 1" + Expect.AreEqual 6, s.Prod(2).Width, "Width of Prod(2) of 3 by 4" + Expect.AreEqual 12, s.Prod2(2, 3).Height, "Height of Prod2(2, 3) of 3 by 4" + Expect.AreEqual 1.5, s.Quot(2).Width, "Width of Quot(2) of 3 by 4" + Expect.AreEqual 1, s.Quot2(2, 4).Height, "Height of Quot2(2, 4) of 3 by 4" + Expect.AreEqual "3,4", s.Width & "," & s.Height, "the size after the query methods" + Expect.Done + End Sub + + [TestCase] + Public Sub Equals_IsExact() + Expect.Begin + Dim s As GpSizeF = GpSizeF(3, 4) + Expect.IsTrue s.Equals(GpSizeF(3, 4)), "Equals of 3 by 4 and 3 by 4" + Expect.IsFalse s.Equals(GpSizeF(3, 4.0001)), "Equals of 3 by 4 and 3 by 4.0001" + Expect.Done + End Sub + +End Module diff --git a/test/package-tests/GdiPlus/Sources/Ty_GpSizeTests.twin b/test/package-tests/GdiPlus/Sources/Ty_GpSizeTests.twin new file mode 100644 index 00000000..2a12d3ac --- /dev/null +++ b/test/package-tests/GdiPlus/Sources/Ty_GpSizeTests.twin @@ -0,0 +1,136 @@ +' Ty_GpSizeTests: the GpSize type (page Types/GpSize). +[TestFixture] +Module Ty_GpSizeTests + + [TestCase] + Public Sub GpSize_IsEightBytes() + Expect.Begin + Dim s As GpSize + Expect.AreEqual 8, LenB(s), "LenB of a GpSize" + Expect.Done + End Sub + + ' ---- Creating + + [TestCase] + Public Sub GpSize_FormsGiveTheDocumentedValues() + Expect.Begin + Dim s As GpSize = GpSize(3, 4) + Expect.AreEqual "3,4", s.Width & "," & s.Height, "GpSize(3, 4)" + Expect.IsTrue GpSize().Empty, "Empty of GpSize()" + Dim declared As GpSize + Expect.IsTrue declared.Empty, "Empty of a variable declared with Dim" + Expect.Done + End Sub + + [TestCase] + Public Sub GpSize_OfGpSizeFRoundsHalvesToEven() + Expect.Begin + Dim rounded As GpSize = GpSize(GpSizeF(2.5, 3.5)) + Expect.AreEqual "2,4", rounded.Width & "," & rounded.Height, "GpSize(GpSizeF(2.5, 3.5))" + Expect.Done + End Sub + + [TestCase] + Public Sub GpSizeFrom_ReadsTheSizeStructure() + Expect.Begin + Dim winSize As GDIP_SIZE + winSize.cx = 9: winSize.cy = 10 + Dim s As GpSize = GpSizeFrom(winSize) + Expect.AreEqual "9,10", s.Width & "," & s.Height, "GpSizeFrom of a GDIP_SIZE of 9 by 10" + Expect.Done + End Sub + + [TestCase] + Public Sub Empty_IsTrueOnlyForBothDimensionsZero() + Expect.Begin + Expect.IsTrue GpSize(0, 0).Empty, "Empty of 0 by 0" + Expect.IsFalse GpSize(0, 1).Empty, "Empty of 0 by 1" + Expect.IsFalse GpSize(1, 0).Empty, "Empty of 1 by 0" + Expect.Done + End Sub + + ' ---- Conversion + + [TestCase] + Public Sub AsGpSizeF_ReturnsTheSameDimensions() + Expect.Begin + Dim f As GpSizeF = GpSize(5, 6).AsGpSizeF() + Expect.AreEqual "5,6", f.Width & "," & f.Height, "AsGpSizeF of 5 by 6" + Expect.Done + End Sub + + [TestCase] + Public Sub SetFrom_RoundsAGpSizeF() + Expect.Begin + Dim s As GpSize + s.SetFrom GpSizeF(2.5, 3.6) + Expect.AreEqual "2,4", s.Width & "," & s.Height, "SetFrom GpSizeF(2.5, 3.6)" + Expect.Done + End Sub + + [TestCase] + Public Sub AsSIZE_And_SetFromSIZE_ConvertToAndFromTheSizeStructure() + Expect.Begin + Dim s As GpSize = GpSize(5, 6) + Dim winSize As GDIP_SIZE = s.AsSIZE() + Expect.AreEqual "5,6", winSize.cx & "," & winSize.cy, "AsSIZE of 5 by 6" + winSize.cx = 9: winSize.cy = 10 + s.SetFromSIZE winSize + Expect.AreEqual "9,10", s.Width & "," & s.Height, "SetFromSIZE of 9 by 10" + Expect.Done + End Sub + + [TestCase] + Public Sub AsGpRect_AsGpRectF_AsRECT_ReturnARectangleAtTheOrigin() + Expect.Begin + Dim s As GpSize = GpSize(5, 6) + Dim r As GpRect = s.AsGpRect() + Expect.AreEqual "0,0,5,6", r.X & "," & r.Y & "," & r.Width & "," & r.Height, "AsGpRect of 5 by 6" + Dim rf As GpRectF = s.AsGpRectF() + Expect.AreEqual "0,0,5,6", rf.X & "," & rf.Y & "," & rf.Width & "," & rf.Height, "AsGpRectF of 5 by 6" + Dim edges As GDIP_RECT = s.AsRECT() + Expect.AreEqual "0,0,5,6", edges.Left & "," & edges.Top & "," & edges.Right & "," & edges.Bottom, "AsRECT of 5 by 6" + Expect.Done + End Sub + + ' ---- Add and Sub change the size, Sum and Diff do not + + [TestCase] + Public Sub Add_ChangesTheSize() + Expect.Begin + Dim s As GpSize = GpSize(3, 4) + s.Add GpSize(1, 1) + Expect.AreEqual "4,5", s.Width & "," & s.Height, "the size 3 by 4 after Add GpSize(1, 1)" + Expect.Done + End Sub + + [TestCase] + Public Sub Sub_ChangesTheSize() + Expect.Begin + Dim s As GpSize = GpSize(4, 5) + s.Sub GpSize(2, 2) + Expect.AreEqual "2,3", s.Width & "," & s.Height, "the size 4 by 5 after Sub GpSize(2, 2)" + Expect.Done + End Sub + + [TestCase] + Public Sub SumAndDiff_ReturnNewSizesAndLeaveTheSize() + Expect.Begin + Dim s As GpSize = GpSize(2, 3) + Expect.AreEqual 12, s.Sum(GpSize(10, 10)).Width, "Width of Sum of 2 by 3 and 10 by 10" + Expect.AreEqual 2, s.Diff(GpSize(1, 1)).Height, "Height of Diff of 2 by 3 and 1 by 1" + Expect.AreEqual "2,3", s.Width & "," & s.Height, "the size after Sum and Diff" + Expect.Done + End Sub + + [TestCase] + Public Sub Equals_ComparesBothDimensions() + Expect.Begin + Dim s As GpSize = GpSize(2, 3) + Expect.IsTrue s.Equals(GpSize(2, 3)), "Equals of 2 by 3 and 2 by 3" + Expect.IsFalse s.Equals(GpSize(2, 4)), "Equals of 2 by 3 and 2 by 4" + Expect.Done + End Sub + +End Module diff --git a/test/package-tests/GdiPlus/Sources/Ty_StartupTests.twin b/test/package-tests/GdiPlus/Sources/Ty_StartupTests.twin new file mode 100644 index 00000000..536a939b --- /dev/null +++ b/test/package-tests/GdiPlus/Sources/Ty_StartupTests.twin @@ -0,0 +1,160 @@ +' Ty_StartupTests: GdiplusStartupInput and GdiplusStartupOutput (pages Types/GdiplusStartupInput and +' Types/GdiplusStartupOutput). +[TestFixture] +Module Ty_StartupTests + + ' A token of the hook function as the delegate declares it (a Long), with the four bytes after it. + Private Type Ty_TokenAndNeighbour + token As Long + neighbour As Long + End Type + + ' ---- GdiplusStartupInput + + [TestCase] + Public Sub Declared_VersionZeroIsRefused() + Expect.Begin + Dim blank As GdiplusStartupInput + Expect.AreEqual 0, blank.GdiplusVersion, "GdiplusVersion of a variable declared with Dim" + Dim failed As GdiPlusUser = GdiPlusUser(blank) + Expect.AreEqual 17, CLng(failed.LastResult), "LastResult of GdiPlusUser(blank) (UnsupportedGdiplusVersion)" + Expect.Done + End Sub + + [TestCase] + Public Sub Fields_VersionTwoWithStartupParametersStarts() + Expect.Begin + Dim settings As GdiplusStartupInput + settings.GdiplusVersion = 2 + settings.StartupParameters = GdiplusStartupNoSetRound + Dim custom As GdiPlusUser = GdiPlusUser(settings) + Expect.AreEqual 0, CLng(custom.LastResult), "LastResult of GdiPlusUser(settings) with GdiplusVersion 2 and GdiplusStartupNoSetRound" + Expect.AreEqual 1, CLng(settings.StartupParameters), "StartupParameters after it was set to GdiplusStartupNoSetRound" + Expect.Done + End Sub + + [TestCase] + Public Sub SuppressBackgroundThread_InputOnlyConstructorFails() + Expect.Begin + Dim settings As GdiplusStartupInput + settings.GdiplusVersion = 1 + settings.SuppressBackgroundThread = True + Dim failed As GdiPlusUser = GdiPlusUser(settings) + Expect.AreEqual 2, CLng(failed.LastResult), "LastResult of GdiPlusUser(settings) with SuppressBackgroundThread = True (InvalidParameter)" + Expect.Done + End Sub + + [TestCase] + Public Sub SuppressBackgroundThread_ConstructorWithOutputStarts() + Expect.Begin + Dim settings As GdiplusStartupInput + settings.GdiplusVersion = 1 + settings.SuppressBackgroundThread = True + Dim output As GdiplusStartupOutput + Dim user As GdiPlusUser = GdiPlusUser(settings, output) + Expect.AreEqual 0, CLng(user.LastResult), "LastResult of GdiPlusUser(settings, output) with SuppressBackgroundThread = True" + Expect.Done + End Sub + + [TestCase] + Public Sub Function_NoArgumentsGivesVersionOne() + Expect.Begin + Dim settings As GdiplusStartupInput = GdiplusStartupInput() + Expect.AreEqual 1, settings.GdiplusVersion, "GdiplusVersion of GdiplusStartupInput()" + Expect.AreEqual 0, CLng(settings.StartupParameters), "StartupParameters of GdiplusStartupInput()" + Expect.Done + End Sub + + ' The second form: a startup parameter first, version 2. + [TestCase] + Public Sub Function_StartupParametersFormGivesVersionTwo() + Expect.Begin + Dim settings As GdiplusStartupInput = GdiplusStartupInput(GdiplusStartupNoSetRound) + Expect.AreEqual 2, settings.GdiplusVersion, "GdiplusVersion of GdiplusStartupInput(GdiplusStartupNoSetRound)" + Expect.AreEqual 1, CLng(settings.StartupParameters), "StartupParameters of GdiplusStartupInput(GdiplusStartupNoSetRound)" + Expect.Done + End Sub + + ' The third form: a version first. + [TestCase] + Public Sub Function_VersionFormGivesThatVersion() + Expect.Begin + Dim settings As GdiplusStartupInput = GdiplusStartupInput(3, GdiplusStartupDefault) + Expect.AreEqual 3, settings.GdiplusVersion, "GdiplusVersion of GdiplusStartupInput(3, GdiplusStartupDefault)" + Expect.AreEqual 0, CLng(settings.StartupParameters), "StartupParameters of GdiplusStartupInput(3, GdiplusStartupDefault)" + Expect.Done + End Sub + + ' ---- GdiplusStartupOutput + + [TestCase] + Public Sub Output_HooksAreSetAfterStartingWithTheOutput() + Expect.Begin + Dim settings As GdiplusStartupInput + settings.GdiplusVersion = 1 + settings.SuppressBackgroundThread = True + Dim output As GdiplusStartupOutput + Expect.IsTrue output.NotificationHook = 0, "NotificationHook before GDI+ is started with the output" + Expect.IsTrue output.NotificationUnhook = 0, "NotificationUnhook before GDI+ is started with the output" + Dim user As GdiPlusUser = GdiPlusUser(settings, output) + Expect.IsTrue output.NotificationHook <> 0, "NotificationHook after GDI+ is started with the output" + Expect.IsTrue output.NotificationUnhook <> 0, "NotificationUnhook after GDI+ is started with the output" + Expect.Done + End Sub + + [TestCase] + Public Sub ModuleFunctions_HookAndUnhookWithALongPtrToken() + Expect.Begin + Dim settings As GdiplusStartupInput + settings.GdiplusVersion = 1 + settings.SuppressBackgroundThread = True + Dim output As GdiplusStartupOutput + Dim user As GdiPlusUser = GdiPlusUser(settings, output) + Dim token As LongPtr + Dim status As GpStatus = NotificationHook(token) + Expect.AreEqual 0, CLng(status), "the status of the module function NotificationHook(token) after GDI+ was started with the output" + Expect.IsTrue token <> 0, "the token that the module function NotificationHook returned" + If status = Ok Then NotificationUnhook token + Expect.Done + End Sub + + ' The function in the output structure works with a Long token on a 32-bit program, and with the first + ' element of an array of two Longs on both. + [TestCase] + Public Sub NotificationHookProc_HooksAndUnhooksWithTheFirstElementOfAnArray() + Expect.Begin + Dim settings As GdiplusStartupInput + settings.GdiplusVersion = 1 + settings.SuppressBackgroundThread = True + Dim output As GdiplusStartupOutput + Dim user As GdiPlusUser = GdiPlusUser(settings, output) + Dim slot(0 To 1) As Long + slot(1) = &H12345678 + Dim status As GpStatus = output.NotificationHook(slot(0)) + Expect.AreEqual 0, CLng(status), "the status of NotificationHookProc with the first element of an array of two Longs" + Expect.IsTrue slot(0) <> 0, "the token that NotificationHookProc returned" + output.NotificationUnhook slot(0) + Expect.Done + End Sub + + ' The delegate takes the token as a Long. GDI+ writes a pointer-sized value, which in a 64-bit program + ' is eight bytes: the neighbour of the variable must stay as it was. + [TestCase] + Public Sub NotificationHookProc_DoesNotWriteBeyondTheToken() + Expect.Begin + Dim settings As GdiplusStartupInput + settings.GdiplusVersion = 1 + settings.SuppressBackgroundThread = True + Dim output As GdiplusStartupOutput + Dim user As GdiPlusUser = GdiPlusUser(settings, output) + Dim t As Ty_TokenAndNeighbour + t.neighbour = &H12345678 + Dim status As GpStatus = output.NotificationHook(t.token) + Dim neighbour As Long = t.neighbour + output.NotificationUnhook t.token + Expect.AreEqual 0, CLng(status), "NotificationHookProc called through the output" + Expect.AreEqual "12345678", Ty_Hex8(neighbour), "the four bytes after the Long token variable after NotificationHookProc wrote the token" + Expect.Done + End Sub + +End Module diff --git a/test/package-tests/GdiPlus/Sources/Ty_Support.twin b/test/package-tests/GdiPlus/Sources/Ty_Support.twin new file mode 100644 index 00000000..c50fac80 --- /dev/null +++ b/test/package-tests/GdiPlus/Sources/Ty_Support.twin @@ -0,0 +1,49 @@ +' Ty_Support: what the test cases of the value types (the Ty_ modules) share. Nothing here is a test case. +Module Ty_Support + + ' A colour as eight hexadecimal digits, so that a report reads FF223344 and not -14400700. + Public Function Ty_Hex8(ByVal Value As Long) As String + Ty_Hex8 = Right$("00000000" & Hex$(Value), 8) + End Function + + ' Checks that two Single values are the same within Tolerance; a failure reads as AreEqual's does. + Public Sub Ty_Near(ByVal Expected As Double, ByVal Actual As Double, ByVal Tolerance As Double, ByVal What As String) + If Abs(Expected - Actual) > Tolerance Then Expect.Fail What & ": expected " & Expected & ", got " & Actual + End Sub + + ' The pixel at (1, 1) of a 4 x 4 bitmap that was cleared to Background and then filled with Brush. + Public Function Ty_FillOver(ByVal Background As Color, ByRef Brush As SolidBrush) As Color + Dim bmp As Bitmap = Bitmap(4!, 4!) + Dim g As Graphics = Graphics(bmp) + g.Clear Background + g.FillRectangle Brush, 0, 0, 4, 4 + Return bmp.Pixel(1, 1) + End Function + + ' The pixel at (4, 4) of an 8 x 8 bitmap that was cleared to Background and crossed by a line of Stroke + ' along y = 4.5, which fills exactly row 4. + Public Function Ty_LineOver(ByVal Background As Color, ByRef Stroke As Pen) As Color + Dim bmp As Bitmap = Bitmap(8!, 8!) + Dim g As Graphics = Graphics(bmp) + g.Clear Background + g.DrawLine Stroke, 0!, 4.5!, 8!, 4.5! + Return bmp.Pixel(4, 4) + End Function + + ' The pixel at (1, 1) of a 4 x 4 bitmap that was cleared with the Long overload of Graphics.Clear. + Public Function Ty_ClearedWithLong(ByVal Colour As Long) As Color + Dim bmp As Bitmap = Bitmap(4!, 4!) + Dim g As Graphics = Graphics(bmp) + g.Clear Colour + Return bmp.Pixel(1, 1) + End Function + + ' The pixel at (1, 1) of a 4 x 4 bitmap that was cleared with the Color overload of Graphics.Clear. + Public Function Ty_ClearedWith(ByVal Colour As Color) As Color + Dim bmp As Bitmap = Bitmap(4!, 4!) + Dim g As Graphics = Graphics(bmp) + g.Clear Colour + Return bmp.Pixel(1, 1) + End Function + +End Module diff --git a/test/package-tests/GdiPlus/compile/Fx_CompileTests.twin b/test/package-tests/GdiPlus/compile/Fx_CompileTests.twin new file mode 100644 index 00000000..bab880b2 --- /dev/null +++ b/test/package-tests/GdiPlus/compile/Fx_CompileTests.twin @@ -0,0 +1,13 @@ +' GdiPlus, effects and text: what the compiler refuses. Each line ends in a comment ' CASE : . +Module Fx_CompileTests + + Public Sub Fx_NewFont_WithoutThePrefix() + Dim f As GdiPlus.Font = New Font(GenericSansSerif(), 12!) ' CASE NewFontWithoutPrefix: none + End Sub + + Public Sub Fx_LevelsParams_HighlightField() + Dim p As LevelsParams + p.highlight = 90 ' CASE LevelsParamsHighlightField: none + End Sub + +End Module diff --git a/test/package-tests/GdiPlus/compile/Gr_CompileTests.twin b/test/package-tests/GdiPlus/compile/Gr_CompileTests.twin new file mode 100644 index 00000000..24e12e82 --- /dev/null +++ b/test/package-tests/GdiPlus/compile/Gr_CompileTests.twin @@ -0,0 +1,53 @@ +' GdiPlus, graphics and the library: what the compiler refuses. Each line ends in a comment ' CASE : . +' The project references the VB package and the OLE Automation library, as every default project does, +' so a bare Font is the type of stdole. +Module Gr_CompileTests + + Public Sub Gr_BaseAlloc() + Dim block As LongPtr = GdiPlusBase.Alloc(64) ' CASE GrBaseAllocIsNotRecognised: error + End Sub + + Public Sub Gr_UserAlloc() + Dim block As LongPtr = GdiPlusUser.Alloc(64) ' CASE GrUserAllocCompiles: none + End Sub + + Public Sub Gr_QualifiedImage() + Dim bmp As Bitmap = Bitmap(4!, 4!) + Dim img As GdiPlus.Image = bmp ' CASE GrQualifiedImageCompiles: none + End Sub + + Public Sub Gr_QualifiedFont() + Dim f As GdiPlus.Font = GdiPlus.Font("Arial", 14) ' CASE GrQualifiedFontCompiles: none + End Sub + + Public Sub Gr_NewBareFont() + Dim f As Font = New Font("Arial", 14) ' CASE GrNewBareFontIsNotThePackagesClass: error + End Sub + + Public Sub Gr_DriverStringWithSolidBrushVariable() + Dim g As Graphics = Graphics(Bitmap(4!, 4!)) + Dim f As GdiPlus.Font = GdiPlus.Font("Arial", 14) + Dim brush As SolidBrush = SolidBrush(Color(0, 0, 0)) + Dim options As GpDriverStringOptions + Dim noMatrix As Matrix = Nothing + g.DrawDriverString("Hi", f, brush, GpPointF(5, 5), options, noMatrix) ' CASE GrDriverStringSolidBrushVariable: TB5073 + End Sub + + Public Sub Gr_DriverStringWithZeroFlags() + Dim g As Graphics = Graphics(Bitmap(4!, 4!)) + Dim f As GdiPlus.Font = GdiPlus.Font("Arial", 14) + Dim brush As Brush = SolidBrush(Color(0, 0, 0)) + Dim noMatrix As Matrix = Nothing + g.DrawDriverString("Hi", f, brush, GpPointF(5, 5), 0, noMatrix) ' CASE GrDriverStringZeroFlags: TB5073 + End Sub + + Public Sub Gr_DriverStringWithBrushVariableAndOptions() + Dim g As Graphics = Graphics(Bitmap(4!, 4!)) + Dim f As GdiPlus.Font = GdiPlus.Font("Arial", 14) + Dim brush As Brush = SolidBrush(Color(0, 0, 0)) + Dim options As GpDriverStringOptions + Dim noMatrix As Matrix = Nothing + g.DrawDriverString("Hi", f, brush, GpPointF(5, 5), options, noMatrix) ' CASE GrDriverStringBrushVariableCompiles: none + End Sub + +End Module diff --git a/test/package-tests/GdiPlus/compile/Im_CompileTests.twin b/test/package-tests/GdiPlus/compile/Im_CompileTests.twin new file mode 100644 index 00000000..3a45664c --- /dev/null +++ b/test/package-tests/GdiPlus/compile/Im_CompileTests.twin @@ -0,0 +1,34 @@ +' GdiPlus, images: what the compiler refuses. Each line ends in a comment ' CASE : . +' The project references the VB package, as every default project does, so a bare Image is its control class. +Module Im_CompileTests + + Public Sub Im_BareNewImage() + Dim img As Image = New Image("picture.png") ' CASE ImBareNewImageTakesNoArguments: TB5030 + End Sub + + Public Sub Im_BareImageVariable() + Dim v As Image + Dim s As Boolean = v.Stretch ' CASE ImBareImageHasTheVbMembers: none + End Sub + + Public Sub Im_QualifiedImageVariable() + Dim v As GdiPlus.Image + Dim s As Boolean = v.Stretch ' CASE ImQualifiedImageHasNoVbMembers: error + End Sub + + Public Sub Im_QualifiedImageCompiles() + Dim img As GdiPlus.Image = New GdiPlus.Image("picture.png") ' CASE ImQualifiedNewCompiles: none + Dim same As GdiPlus.Image = GdiPlus.Image("picture.png") ' CASE ImQualifiedFunctionCompiles: none + End Sub + + Public Sub Im_BitmapCloneWithoutArguments() + Dim bmp As Bitmap = Bitmap(4!, 4!) + Dim copy As Bitmap = bmp.Clone() ' CASE ImBitmapCloneWithoutArguments: error + End Sub + + Public Sub Im_ImageCloneWithoutArguments() + Dim img As GdiPlus.Image = GdiPlus.Image("picture.png") + Dim copy As GdiPlus.Image = img.Clone() ' CASE ImImageCloneWithoutArguments: none + End Sub + +End Module diff --git a/test/package-tests/GdiPlus/compile/Ty_CompileTests.twin b/test/package-tests/GdiPlus/compile/Ty_CompileTests.twin new file mode 100644 index 00000000..4e5ff861 --- /dev/null +++ b/test/package-tests/GdiPlus/compile/Ty_CompileTests.twin @@ -0,0 +1,20 @@ +' GdiPlus, value types: what the compiler refuses. Each line ends in a comment ' CASE : . +Module Ty_CompileTests + + Public Sub Ty_PointFScale() + Dim p As GpPointF = GpPointF(3, 4) + p.Scale(2) ' CASE PointFScaleWithParenthesesIsASyntaxError: error + p.Scale 2 ' CASE PointFScaleWithoutParenthesesIsAccepted: none + Call p.Scale(2) ' CASE PointFScaleWithCallIsAccepted: none + p.Scale2 1, 2 ' CASE PointFScale2IsAccepted: none + End Sub + + Public Sub Ty_SizeFScale() + Dim s As GpSizeF = GpSizeF(3, 4) + s.Scale(2) ' CASE SizeFScaleWithParenthesesIsASyntaxError: error + s.Scale 2 ' CASE SizeFScaleWithoutParenthesesIsAccepted: none + Call s.Scale(2) ' CASE SizeFScaleWithCallIsAccepted: none + s.Scale2 1, 2 ' CASE SizeFScale2IsAccepted: none + End Sub + +End Module diff --git a/test/package-tests/GdiPlus/expect.json b/test/package-tests/GdiPlus/expect.json new file mode 100644 index 00000000..807b2801 --- /dev/null +++ b/test/package-tests/GdiPlus/expect.json @@ -0,0 +1,643 @@ +{ + "version": "0.9.45.0", + "cases": { + "Gr_GraphicsTests.Graphics_NothingImage_ReportsError": { + "fails": "1 of 1 checks failed: LastResult of Graphics(Nothing) is an error status: expected True, got False" + }, + "Gr_GraphicsTests.NewGraphics_NothingImage_ReportsError": { + "fails": "1 of 1 checks failed: LastResult of New Graphics(Nothing) is an error status: expected True, got False" + }, + "Gr_GraphicsTests.Clear_VBColourRed_FillsRed": { + "fails": "1 of 1 checks failed: pixel after Clear(vbRed): expected \"FFFF0000\", got \"FF0000FF\"" + }, + "Gr_GraphicsTests.Clear_RGBFunction_FillsThatColour": { + "fails": "2 of 2 checks failed: pixel after Clear(RGB(255, 0, 0)): expected \"FFFF0000\", got \"FF0000FF\"; pixel after Clear(RGB(10, 20, 30)): expected \"FF0A141E\", got \"FF1E140A\"" + }, + "Gr_GraphicsTests.Clear_CColorTransparent_KeepsAlpha": { + "fails": "1 of 1 checks failed: alpha of a white bitmap after Clear(CColor.Transparent): expected 0, got 255" + }, + "Gr_GraphicsTests.DrawImage_DestRectF_ScalesTheImage": { + "win32": { + "fails": "(runtime error -2147467259: NATIVE EXCEPTION: ACCESS_VIOLATION)" + }, + "win64": "pass" + }, + "Gr_GraphicsTests.DrawImage_DestRect_ScalesTheImage": { + "win32": { + "fails": "(runtime error -2147467259: NATIVE EXCEPTION: ACCESS_VIOLATION)" + }, + "win64": "pass" + }, + "Gr_GraphicsTests.DrawImage_ThreeFloatPointsAndSourcePart_DrawsTheImage": { + "fails": "(runtime error -2147467259: NATIVE EXCEPTION: ACCESS_VIOLATION)" + }, + "Gr_GraphicsTests.Clip_Get_ReturnsTheClippingRegion": { + "fails": "2 of 2 checks failed: Clip returns a Region after SetClip: expected True, got False; LastResult after reading Clip: expected 0, got 2" + }, + "Gr_GraphicsTests.IsVisible_GpPointF_ReturnsWhetherThePointIsInside": { + "win32": { + "fails": "(runtime error -2147467259: NATIVE EXCEPTION: ACCESS_VIOLATION)" + }, + "win64": "pass" + }, + "Gr_GraphicsTests.Transform_Get_ReturnsTheWorldTransformation": { + "fails": "2 of 2 checks failed: Graphics.Transform returns a Matrix: expected True, got False; LastResult after reading Transform: expected 0, got 2" + }, + "Gr_GraphicsTests.ShearTransform_ShearsTheCoordinates": { + "fails": "2 of 3 checks failed: status of ShearTransform(0.5, 0): expected 0, got 2; X of the point (1, 2) after ShearTransform(0.5, 0): x moves by half of y: expected 2, got 1" + }, + "Gr_LibraryTests.GdiPlusUser_BitmapOfTwoWholeNumbers_IsCreated": { + "fails": "3 of 3 checks failed: LastResult of Bitmap(64, 64): expected 0, got 7; Width of Bitmap(64, 64): expected 64, got 0; Height of Bitmap(64, 64): expected 64, got 0" + }, + "Gr_LibraryTests.TestControlForceBilinear_Method_ReturnsOk": { + "fails": "(runtime error -2147467259: NATIVE EXCEPTION: STACK_OVERFLOW)" + }, + "Gr_LibraryTests.TestControlNoICM_Method_ReturnsOk": { + "fails": "(runtime error -2147467259: NATIVE EXCEPTION: STACK_OVERFLOW)" + }, + "Gr_LibraryTests.SetAbort_ThenDrawingTheImage_Works": { + "fails": "(runtime error -2147467259: NATIVE EXCEPTION: ACCESS_VIOLATION)" + }, + "Gr_LibraryTests.IsCanonicalPixelFormat_PremultipliedFormats_AreCanonical": { + "fails": "2 of 5 checks failed: 32bppPARGB is canonical: expected True, got False; 64bppPARGB is canonical: expected True, got False" + }, + "Gr_LibraryTests.GdipCreateStreamOnFile_ExistingFile_ReturnsAStream": { + "fails": "2 of 2 checks failed: status of GdipCreateStreamOnFile for an existing file, opened for reading: expected 0, got 6; GdipCreateStreamOnFile stores a stream: expected True, got False" + }, + "Gr_LibraryTests.PropertyTag_ConstantsAbove8000_AreNotNegativeInLong": { + "fails": "2 of 3 checks failed: PropertyTagCopyright assigned to a Long, expected 33432 (&H8298): expected 33432, got -32104; PropertyTagExifIFD assigned to a Long, expected 34665 (&H8769): expected 34665, got -30871" + }, + "Gr_LibraryTests.RemovePropertyItem_HighPropertyTag_RemovesTheItem": { + "fails": "2 of 3 checks failed: status of RemovePropertyItem(PropertyTagCopyright): expected 0, got 19; PropertyCount after RemovePropertyItem(PropertyTagCopyright): expected 0, got 1" + }, + "Gr_LibraryTests.GdiplusStartupParams_ReservedMembers_AreSingleBits": { + "fails": "2 of 2 checks failed: GdiplusStartupReserved1 is a single bit, as a member of a flags enumeration is: expected True, got False; GdiplusStartupReserved2 is a single bit, as a member of a flags enumeration is: expected True, got False" + }, + "Gr_MatrixTests.Matrix_RectFAndPoint_MovesTheRectangleToThePoint": { + "fails": "1 of 2 checks failed: the bottom-right corner of the 10 by 10 rectangle maps to (15, 15): expected True, got False" + }, + "Gr_MatrixTests.Matrix_RectAndPoint_MovesTheRectangleToThePoint": { + "fails": "1 of 2 checks failed: the bottom-right corner of the 10 by 10 rectangle maps to (15, 15): expected True, got False" + }, + "Gr_MatrixTests.OffsetX_Assign_ChangesOnlyDx": { + "fails": "1 of 2 checks failed: OffsetY after assigning 7 to OffsetX: expected 4, got 0" + }, + "Gr_MatrixTests.OffsetY_Assign_ChangesOnlyDy": { + "fails": "2 of 2 checks failed: OffsetX after assigning 9 to OffsetY: expected 3, got 9; OffsetY after assigning 9: expected 9, got 0" + }, + "Gr_MatrixTests.Scale_StatementWithParentheses_Scales": { + "fails": "2 of 2 checks failed: m11 after the statement m.Scale(2, 3) (a defect of twinBASIC BETA 1005, not of the package): expected 2, got 1; m22 after the statement m.Scale(2, 3) (a defect of twinBASIC BETA 1005, not of the package): expected 3, got 1" + }, + "Gr_MatrixTests.Transform_GetOnMatrix_ReturnsTheMatrix": { + "fails": "1 of 2 checks failed: Transform read through ITransformable on a Matrix returns a Matrix: expected True, got False" + }, + "Gr_MatrixTests.Transform_GetOnGraphics_ThroughTheInterface_ReturnsTheMatrix": { + "fails": "2 of 2 checks failed: Transform read through ITransformable on a Graphics returns a Matrix: expected True, got False; LastResult of the Graphics after reading Transform: expected 0, got 2" + }, + "Gr_MatrixTests.Transform_GetOnGradientBrush_ThroughTheInterface_ReturnsTheMatrix": { + "fails": "2 of 2 checks failed: Transform read through ITransformable on a LinearGradientBrush returns a Matrix: expected True, got False; LastResult of the brush after reading Transform: expected 0, got 2" + }, + "Gr_VBGraphicsTests.Line_VBColourRed_DrawsRed": { + "fails": "1 of 1 checks failed: pixel on a line drawn in vbRed with the default UseTransparentAlpha: expected \"FFFF0000\", got \"FFFFFFFF\"" + }, + "Gr_VBGraphicsTests.Line_VBColourWithoutTransparentAlpha_DrawsThatColour": { + "fails": "2 of 2 checks failed: pixel on a line drawn in vbRed with UseTransparentAlpha False: expected \"FFFF0000\", got \"FF0000FF\"; pixel on a line drawn in RGB(10, 20, 30) with UseTransparentAlpha False: expected \"FF0A141E\", got \"FF1E140A\"" + }, + "Gr_VBGraphicsTests.DrawWidth_BeforeFirstDraw_ReadsOne": { + "fails": "2 of 2 checks failed: DrawWidth of a new VBGraphics: expected 1, got 0; LastResult after reading DrawWidth of a new VBGraphics: expected 0, got 2" + }, + "Gr_VBGraphicsTests.DrawWidth_AssignedBeforeFirstDraw_IsKept": { + "fails": "2 of 2 checks failed: DrawWidth after assigning 4 to a new VBGraphics: expected 4, got 0; LastResult after assigning DrawWidth of a new VBGraphics: expected 0, got 2" + }, + "Gr_VBGraphicsTests.Line_BoxFilled_FillsFromTheFirstCorner": { + "fails": "3 of 4 checks failed: pixel inside the box (5, 5)-(15, 15) drawn with BF: expected \"FFFF0000\", got \"FFFFFFFF\"; pixel below the box: expected \"FFFFFFFF\", got \"FFFF0000\"; last row with red pixels after BF (5, 5)-(15, 15): expected 15, got 24" + }, + "Gr_VBGraphicsTests.Circle_StartAndEnd_DrawAnArc": { + "fails": "2 of 2 checks failed: LastResult after Circle with start and end: expected 0, got 6; pixels drawn by Circle with start 0 and end 3.14: expected True, got False" + }, + "Gr_VBGraphicsTests.PSet_SetsOnlyThePixel": { + "fails": "1 of 4 checks failed: pixel below the PSet point: expected \"FFFFFFFF\", got \"FFFF0000\"" + }, + "Br_BrushColourTests.SolidBrush_VBColour_PaintsThatColour": { + "fails": "4 of 4 checks failed: pixel painted by SolidBrush(vbRed): expected \"FFFF0000\", got \"FF0000FF\"; pixel painted by SolidBrush(vbBlue): expected \"FF0000FF\", got \"FFFF0000\"; pixel painted by SolidBrush(RGB(255, 0, 0)): expected \"FFFF0000\", got \"FF0000FF\"; pixel painted by SolidBrush(RGB(10, 20, 30)): expected \"FF0A141E\", got \"FF1E140A\"" + }, + "Br_BrushColourTests.SolidBrush_SystemColour_IsTranslated": { + "fails": "1 of 1 checks failed: pixel painted by SolidBrush(vbButtonFace), the button face colour: expected \"FFF0F0F0\", got \"FF00000F\"" + }, + "Br_BrushColourTests.SolidBrush_CColorTransparent_PaintsNothing": { + "fails": "1 of 2 checks failed: pixel of a blue bitmap after filling it with SolidBrush(CColor.Transparent): expected \"FF0000FF\", got \"FFFFFFFF\"" + }, + "Br_BrushColourTests.SolidBrush_RGBProperty_Let_TakesAVBColour": { + "fails": "1 of 1 checks failed: Color of a SolidBrush after RGB = vbRed: expected \"FFFF0000\", got \"FF0000FF\"" + }, + "Br_BrushColourTests.SolidBrush_RGBProperty_Get_ReturnsAVBColour": { + "fails": "1 of 1 checks failed: RGB of a SolidBrush whose Color is opaque red, as a VB colour: expected 255, got 16711680" + }, + "Br_BrushColourTests.HatchBrush_RGBForm_VBColour_UsesThatColour": { + "fails": "2 of 2 checks failed: ForegroundColor of HatchBrush(HatchStyleCross, vbRed, vbBlue): expected \"FFFF0000\", got \"FF0000FF\"; BackgroundColor of HatchBrush(HatchStyleCross, vbRed, vbBlue): expected \"FF0000FF\", got \"FFFF0000\"" + }, + "Br_BrushColourTests.LinearGradientBrush_RGBForm_VBColour_UsesThatColour": { + "fails": "2 of 2 checks failed: start colour of a gradient made with vbRed, vbBlue: expected \"FFFF0000\", got \"FF0000FF\"; end colour of a gradient made with vbRed, vbBlue: expected \"FF0000FF\", got \"FFFF0000\"" + }, + "Br_BrushColourTests.LinearGradientBrush_RGBForm_Points_VBColour_UsesThatColour": { + "fails": "2 of 2 checks failed: start colour of a two-point gradient made with vbRed, vbBlue: expected \"FFFF0000\", got \"FF0000FF\"; end colour of a two-point gradient made with vbRed, vbBlue: expected \"FF0000FF\", got \"FFFF0000\"" + }, + "Br_BrushColourTests.LinearGradientBrush_SetLinearColors_RGBForm_VBColour_UsesThatColour": { + "fails": "2 of 2 checks failed: start colour after SetLinearColors vbRed, vbBlue: expected \"FFFF0000\", got \"FF0000FF\"; end colour after SetLinearColors vbRed, vbBlue: expected \"FF0000FF\", got \"FFFF0000\"" + }, + "Br_BrushColourTests.PathGradientBrush_CenterRGB_VBColour_UsesThatColour": { + "fails": "1 of 1 checks failed: CenterColor after CenterRGB = vbRed: expected \"FFFF0000\", got \"FF0000FF\"" + }, + "Br_BrushColourTests.Pen_RGBForm_VBColour_UsesThatColour": { + "fails": "2 of 2 checks failed: Color of Pen(vbRed, 2): expected \"FFFF0000\", got \"FF0000FF\"; Color of Pen(RGB(0, 0, 255)): expected \"FF0000FF\", got \"FFFF0000\"" + }, + "Br_BrushColourTests.Pen_RGBForm_VBColour_DrawsThatColour": { + "fails": "1 of 1 checks failed: pixel on a line drawn with Pen(vbRed, 2): expected \"FFFF0000\", got \"FF0000FF\"" + }, + "Br_BrushColourTests.Pen_RGBProperty_Let_TakesAVBColour": { + "fails": "1 of 1 checks failed: Color of a Pen after RGB = vbRed: expected \"FFFF0000\", got \"FF0000FF\"" + }, + "Br_BrushColourTests.Pen_RGBProperty_Get_ReturnsAVBColour": { + "fails": "1 of 1 checks failed: RGB of a Pen whose Color is opaque red, as a VB colour: expected 255, got 16711680" + }, + "Br_BrushColourTests.Pen_Set_RGBForm_VBColour_UsesThatColour": { + "fails": "1 of 2 checks failed: Color of a Pen after Set vbRed, 3: expected \"FFFF0000\", got \"FF0000FF\"" + }, + "Br_BrushTests.Clone_AfterEarlierFailure_ReturnsTheCopy": { + "fails": "1 of 1 checks failed: Clone of a brush that recorded an earlier failure returns a copy: expected False, got True" + }, + "Br_BrushTests.TextureBrush_Image_AfterEarlierFailure_ReturnsTheImage": { + "fails": "1 of 2 checks failed: Image of a brush that recorded an earlier failure returns the image: expected False, got True" + }, + "Br_BrushTests.Type_PathGradientBrush_ReturnsPathGradient": { + "fails": "(runtime error -353703420: Assertion FAILED)" + }, + "Br_BrushTests.HatchBrush_Function_ColorForm_Returns": { + "fails": "(runtime error -2147467259: NATIVE EXCEPTION: STACK_OVERFLOW)" + }, + "Br_BrushTests.HatchBrush_Function_RGBForm_Returns": { + "fails": "(runtime error -2147467259: NATIVE EXCEPTION: STACK_OVERFLOW)" + }, + "Br_BrushTests.TextureBrush_Transform_Get_ReturnsTheMatrix": { + "fails": "2 of 2 checks failed: Transform returns a matrix: expected False, got True; LastResult after reading Transform: expected 0, got 2" + }, + "Br_CapTests.CustomLineCap_New_BaseInset_KeepsTheFraction": { + "fails": "1 of 1 checks failed: BaseInset of a cap made with the base inset 1.5: expected 1.5, got 2" + }, + "Br_CapTests.CustomLineCap_StartStrokeCap_Let_Returns": { + "fails": "(runtime error -2147467259: NATIVE EXCEPTION: STACK_OVERFLOW)" + }, + "Br_CapTests.CustomLineCap_EndStrokeCap_Let_Returns": { + "fails": "(runtime error -2147467259: NATIVE EXCEPTION: STACK_OVERFLOW)" + }, + "Br_CapTests.CustomLineCap_SetStrokeCaps_WithACap_Returns": { + "fails": "(runtime error -2147467259: NATIVE EXCEPTION: STACK_OVERFLOW)" + }, + "Br_CapTests.ArrowCap_Clone_ReturnsAUsableCopy": { + "fails": "4 of 4 checks failed: Height of the clone: expected 3, got 0; Width of the clone: expected 4, got 0; FillState of the clone: expected True, got False; LastResult of the clone after reading its properties: expected 0, got 2" + }, + "Br_CapTests.ArrowCap_MiddleInset_Get_ReturnsTheSetting": { + "fails": "1 of 1 checks failed: MiddleInset after it was set to 1.5: expected 1.5, got 0" + }, + "Br_CapTests.ArrowCap_StartStrokeCap_Let_Returns": { + "fails": "(runtime error -2147467259: NATIVE EXCEPTION: STACK_OVERFLOW)" + }, + "Br_CapTests.ArrowCap_EndStrokeCap_Let_Returns": { + "fails": "(runtime error -2147467259: NATIVE EXCEPTION: STACK_OVERFLOW)" + }, + "Br_CapTests.ArrowCap_SetStrokeCaps_WithACap_Returns": { + "fails": "(runtime error -2147467259: NATIVE EXCEPTION: STACK_OVERFLOW)" + }, + "Br_GradientBrushTests.Linear_LinearColors_HasTwoElements": { + "fails": "1 of 1 checks failed: number of elements of the array LinearColors returns, for a start and an end colour: expected 2, got 3" + }, + "Br_GradientBrushTests.Linear_LinearRGBs_HasTwoElements": { + "fails": "1 of 1 checks failed: number of elements of the array LinearRGBs returns, for a start and an end colour: expected 2, got 3" + }, + "Br_GradientBrushTests.Linear_SetBlend_TwoArrays_Returns": { + "fails": "(runtime error -2147467259: NATIVE EXCEPTION: ACCESS_VIOLATION)" + }, + "Br_GradientBrushTests.Linear_SetInterpolationColors_TwoArrays_Returns": { + "fails": "(runtime error -2147467259: NATIVE EXCEPTION: ACCESS_VIOLATION)" + }, + "Br_GradientBrushTests.Linear_Transform_Get_ReturnsTheMatrix": { + "fails": "2 of 2 checks failed: Transform returns a matrix: expected False, got True; LastResult after reading Transform: expected 0, got 2" + }, + "Br_GradientBrushTests.Path_New_SinglePoint_ReportsTheFailure": { + "fails": "1 of 1 checks failed: LastResult of a PathGradientBrush made from a single point: expected True, got False" + }, + "Br_GradientBrushTests.Path_SurroundColors_Get_ReturnsWhatWasSet": { + "fails": "(runtime error -2147467259: NATIVE EXCEPTION: STACK_OVERFLOW)" + }, + "Br_GradientBrushTests.Path_SetBlend_Returns": { + "fails": "(runtime error -2147467259: NATIVE EXCEPTION: ACCESS_VIOLATION)" + }, + "Br_GradientBrushTests.Path_SetInterpolationColors_Returns": { + "fails": "(runtime error -2147467259: NATIVE EXCEPTION: ACCESS_VIOLATION)" + }, + "Br_GradientBrushTests.Path_Transform_Get_ReturnsTheMatrix": { + "fails": "2 of 2 checks failed: Transform returns a matrix: expected False, got True; LastResult after reading Transform: expected 0, got 2" + }, + "Br_PathDataTests.Point_Get_ReturnsThePoint": { + "win32": { + "fails": "(runtime error -2147467259: NATIVE EXCEPTION: ACCESS_VIOLATION)" + }, + "win64": "pass" + }, + "Br_PathDataTests.TypedPoint_Get_ReturnsThePointAndItsType": { + "win32": { + "fails": "(runtime error -2147467259: NATIVE EXCEPTION: ACCESS_VIOLATION)" + }, + "win64": "pass" + }, + "Br_PathDataTests.TypedPoints_Get_ReturnsACopy": { + "win32": { + "fails": "(runtime error -2147467259: NATIVE EXCEPTION: ACCESS_VIOLATION)" + }, + "win64": "pass" + }, + "Br_PathDataTests.Point_Let_OutOfRange_LeavesTheMemoryBesideTheArrayAlone": { + "fails": "1 of 1 checks failed: the four bytes after the three points of a PathData are as they were after Point(4) was set: expected True, got False" + }, + "Br_PathDataTests.Type_Let_OutOfRange_LeavesTheMemoryBesideTheArrayAlone": { + "fails": "1 of 1 checks failed: the byte after the eight types of a PathData is as it was after Type(9) was set: expected True, got False" + }, + "Br_PathDataTests.TypedPoint_Let_OutOfRange_LeavesTheMemoryBesideTheArraysAlone": { + "fails": "(runtime error -2147467259: NATIVE EXCEPTION: ACCESS_VIOLATION)" + }, + "Br_PathIteratorTests.NextSubpathPath_UDT_ReturnsTheFigure": { + "fails": "aborted" + }, + "Br_PathIteratorTests.NextSubpathPath_Direct_ReturnsTheFigure": { + "fails": "2 of 3 checks failed: count of the first figure: expected 4, got 0; PointCount of the first figure, a rectangle: expected 4, got 0" + }, + "Br_PathIteratorTests.NextMarkerPath_UDT_ReturnsTheSection": { + "fails": "aborted" + }, + "Br_PathIteratorTests.NextMarkerPath_Direct_ReturnsTheCount": { + "fails": "(runtime error -2147467259: NATIVE EXCEPTION: ACCESS_VIOLATION)" + }, + "Br_PathIteratorTests.NextMarkerPath_Direct_ReturnsAUsablePath": { + "fails": "(runtime error -2147467259: NATIVE EXCEPTION: ACCESS_VIOLATION)" + }, + "Br_PathTests.New_PathData_CreatesThePath": { + "fails": "1 of 1 checks failed: PointCount of a path made from a PathData of three points: expected 3, got 0" + }, + "Br_PathTests.New_TypedPoints_CreatesThePath": { + "fails": "1 of 1 checks failed: PointCount of a path made from three typed points: expected 3, got 0" + }, + "Br_PathTests.New_PointsAndTypes_CreatesThePath": { + "fails": "1 of 1 checks failed: PointCount of a path made from three GpPointF and their types: expected 3, got 0" + }, + "Br_PathTests.New_GpPointsAndTypes_CreatesThePath": { + "win32": { + "fails": "(runtime error -2147467259: NATIVE EXCEPTION: ACCESS_VIOLATION)" + }, + "win64": { + "fails": "2 of 2 checks failed: LastResult of a path made from three GpPoint and their types: expected 0, got 3; PointCount of a path made from three GpPoint and their types: expected 3, got 0" + } + }, + "Br_PathTests.PathData_Get_ReturnsThePoints": { + "fails": "2 of 2 checks failed: PathData returns an object for a path of four points: expected False, got True; LastResult after reading PathData: expected 0, got 2" + }, + "Br_PenTests.Brush_AfterEarlierFailure_ReturnsTheBrush": { + "fails": "1 of 2 checks failed: Brush of a pen that recorded an earlier failure returns the brush: expected False, got True" + }, + "Br_PenTests.CustomStartCap_AfterEarlierFailure_ReturnsTheCap": { + "fails": "1 of 2 checks failed: CustomStartCap of a pen that recorded an earlier failure returns the cap: expected False, got True" + }, + "Br_PenTests.CustomStartCap_ArrowCap_ReturnsAUsableCap": { + "fails": "3 of 4 checks failed: Height of the arrow cap CustomStartCap returns: expected 3, got 0; Width of the arrow cap CustomStartCap returns: expected 4, got 0; LastResult of the arrow cap CustomStartCap returns: expected 0, got 2" + }, + "Br_PenTests.CustomEndCap_Get_ReturnsTheCap": { + "fails": "2 of 3 checks failed: error raised by reading CustomEndCap: expected 0, got 91; CustomEndCap returns the cap: expected False, got True" + }, + "Br_PenTests.CustomEndCap_Get_WithACustomStartCap_ReturnsTheCap": { + "fails": "2 of 2 checks failed: error raised by reading CustomEndCap of a pen with a custom start cap and a custom end cap: expected 0, got 438; CustomEndCap returns the cap: expected False, got True" + }, + "Br_PenTests.Transform_Get_ReturnsTheMatrix": { + "fails": "2 of 2 checks failed: Transform returns a matrix: expected False, got True; LastResult after reading Transform: expected 0, got 2" + }, + "NewFontWithoutPrefix": { + "fails": "expected none on Fx_CompileTests.twin line 5, got TB5074, TB5032" + }, + "LevelsParamsHighlightField": { + "fails": "expected none on Fx_CompileTests.twin line 10, got TB5027" + }, + "Fx_EffectTests.Blur_ParametersGetWithoutSize_ReturnsTheParameters": { + "fails": "2 of 2 checks failed: Blur.LastResult after reading Parameters without a size: expected 0, got 2; Blur.Parameters.radius read without a size: expected 5, got 0" + }, + "Fx_EffectTests.Sharpen_ParametersGetWithoutSize_ReturnsTheParameters": { + "fails": "3 of 3 checks failed: Sharpen.LastResult after reading Parameters without a size: expected 0, got 2; Sharpen.Parameters.radius read without a size: expected 5, got 0; Sharpen.Parameters.amount read without a size: expected 50, got 0" + }, + "Fx_EffectTests.BrightnessContrast_ParametersGetWithoutSize_ReturnsTheParameters": { + "fails": "3 of 3 checks failed: BrightnessContrast.LastResult after reading Parameters without a size: expected 0, got 2; BrightnessContrast.Parameters.brightness read without a size: expected 10, got 0; BrightnessContrast.Parameters.contrast read without a size: expected 20, got 0" + }, + "Fx_EffectTests.HueSaturationLightness_ParametersGetWithoutSize_ReturnsTheParameters": { + "fails": "4 of 4 checks failed: HueSaturationLightness.LastResult after reading Parameters without a size: expected 0, got 2; HueSaturationLightness.Parameters.hue read without a size: expected 120, got 0; HueSaturationLightness.Parameters.saturation read without a size: expected -20, got 0; HueSaturationLightness.Parameters.lightness read without a size: expected 10, got 0" + }, + "Fx_EffectTests.Levels_ParametersGetWithoutSize_ReturnsTheParameters": { + "fails": "4 of 4 checks failed: Levels.LastResult after reading Parameters without a size: expected 0, got 2; Levels.Parameters.highight read without a size: expected 80, got 0; Levels.Parameters.midtone read without a size: expected 50, got 0; Levels.Parameters.shadow read without a size: expected 20, got 0" + }, + "Fx_EffectTests.Tint_ParametersGetWithoutSize_ReturnsTheParameters": { + "fails": "3 of 3 checks failed: Tint.LastResult after reading Parameters without a size: expected 0, got 2; Tint.Parameters.hue read without a size: expected 100, got 0; Tint.Parameters.amount read without a size: expected 50, got 0" + }, + "Fx_EffectTests.ColorBalance_ParametersGetWithoutSize_ReturnsTheParameters": { + "fails": "4 of 4 checks failed: ColorBalance.LastResult after reading Parameters without a size: expected 0, got 2; ColorBalance.Parameters.cyanRed read without a size: expected 10, got 0; ColorBalance.Parameters.magentaGreen read without a size: expected -20, got 0; ColorBalance.Parameters.yellowBlue read without a size: expected 30, got 0" + }, + "Fx_EffectTests.ColorCurve_ParametersGetWithoutSize_ReturnsTheParameters": { + "fails": "4 of 4 checks failed: ColorCurve.LastResult after reading Parameters without a size: expected 0, got 2; ColorCurve.Parameters.adjustment read without a size: expected 2, got 0; ColorCurve.Parameters.channel read without a size: expected 1, got 0; ColorCurve.Parameters.value read without a size: expected 20, got 0" + }, + "Fx_EffectTests.ColorMatrixEffect_ParametersGetWithoutSize_ReturnsTheParameters": { + "fails": "3 of 3 checks failed: ColorMatrixEffect.LastResult after reading Parameters without a size: expected 0, got 2; ColorMatrixEffect.Parameters.m(2, 0) read without a size: expected 1, got 0; ColorMatrixEffect.Parameters.m(1, 1) read without a size: expected 1, got 0" + }, + "Fx_EffectTests.ColorLUT_ParametersGetWithoutSize_ReturnsTheParameters": { + "fails": "3 of 3 checks failed: ColorLUT.LastResult after reading Parameters without a size: expected 0, got 2; ColorLUT.Parameters.lutB.b(10) read without a size: expected 245, got 0; ColorLUT.Parameters.lutG.b(10) read without a size: expected 10, got 0" + }, + "Fx_EffectTests.RedEyeCorrection_ParametersGetWithoutSize_SetsNoError": { + "fails": "1 of 1 checks failed: RedEyeCorrection.LastResult after reading Parameters without a size: expected 0, got 2" + }, + "Fx_EffectTests.ParameterSize_BrightnessContrast_IsTheSizeOfItsParameters": { + "fails": "1 of 1 checks failed: BrightnessContrast.ParameterSize: expected 8, got 0" + }, + "Fx_EffectTests.ParameterSize_ColorMatrixEffect_IsTheSizeOfItsParameters": { + "fails": "1 of 1 checks failed: ColorMatrixEffect.ParameterSize: expected 100, got 0" + }, + "Fx_EffectTests.ColorBalance_NewWithParams_StoresTheParameters": { + "fails": "(runtime error -2147467259: NATIVE EXCEPTION: ACCESS_VIOLATION)" + }, + "Fx_EffectTests.ColorCurve_NewWithParams_StoresTheParameters": { + "fails": "(runtime error -2147467259: NATIVE EXCEPTION: ACCESS_VIOLATION)" + }, + "Fx_EffectTests.ColorLUT_NewWithParams_StoresTheParameters": { + "fails": "(runtime error -2147467259: NATIVE EXCEPTION: ACCESS_VIOLATION)" + }, + "Fx_EffectTests.ColorMatrixEffect_NewWithParams_StoresTheMatrix": { + "fails": "(runtime error -2147467259: NATIVE EXCEPTION: ACCESS_VIOLATION)" + }, + "Fx_EffectTests.HueSaturationLightness_NewWithParams_StoresTheParameters": { + "fails": "(runtime error -2147467259: NATIVE EXCEPTION: ACCESS_VIOLATION)" + }, + "Fx_EffectTests.Levels_NewWithParams_StoresTheParameters": { + "fails": "(runtime error -2147467259: NATIVE EXCEPTION: ACCESS_VIOLATION)" + }, + "Fx_EffectTests.ColorLUT_NewWithFourArrays_TakesTheAlphaTableFromLutA": { + "fails": "1 of 5 checks failed: lutA.b(10) after the four-array form: expected 10, got 245" + }, + "Fx_EffectTests.ColorLUTParams_FromFourArrays_TakesTheAlphaTableFromLutA": { + "fails": "1 of 4 checks failed: lutA.b(10) of ColorLUTParams(...): expected 10, got 245" + }, + "Fx_EffectTests.ColorLUT_ApplyEffectWithFourArrays_KeepsAlphaWithTheIdentityTable": { + "fails": "1 of 2 checks failed: pixel after inverting blue and keeping alpha, red and green: expected \"FFC864CD\", got \"00C864CD\"" + }, + "Fx_EffectTests.RedEyeCorrection_NewWithParams_StoresTheAreas": { + "win32": "pass", + "win64": { + "fails": "(runtime error -2147467259: NATIVE EXCEPTION: ACCESS_VIOLATION)" + } + }, + "Fx_EffectTests.RedEyeCorrection_NewKeywordWithParams_StoresTheAreas": { + "win32": "pass", + "win64": { + "fails": "(runtime error -2147467259: NATIVE EXCEPTION: ACCESS_VIOLATION)" + } + }, + "Fx_EffectTests.RedEyeCorrection_ParametersGet_AfterSettingAreas_ReturnsTheAreaCount": { + "fails": "2 of 3 checks failed: RedEyeCorrection.LastResult after reading Parameters with a size: expected 0, got 2; numberOfAreas read back: expected 2, got 0" + }, + "Fx_EffectTests.RedEyeCorrectionParams_FromArray_CopiesTheAreas": { + "fails": "(runtime error -2147467259: NATIVE EXCEPTION: ACCESS_VIOLATION)" + }, + "Fx_FontCollectionTests.InstalledFontCollection_Create_ReturnsACollection": { + "fails": "2 of 2 checks failed: Err.Number after InstalledFontCollection() (Object doesn't support this property or method): expected 0, got 438; InstalledFontCollection() returns an object: expected False, got True" + }, + "Fx_FontCollectionTests.InstalledFontCollection_New_ReturnsACollection": { + "fails": "2 of 2 checks failed: Err.Number after New InstalledFontCollection (Object doesn't support this property or method): expected 0, got 438; New InstalledFontCollection returns an object: expected False, got True" + }, + "Fx_FontCollectionTests.InstalledFontCollection_FamilyCount_CountsTheInstalledFamilies": { + "fails": "1 of 1 checks failed: InstalledFontCollection() returned Nothing" + }, + "Fx_FontCollectionTests.InstalledFontCollection_Families_IsOneBasedAndHoldsAnArialFamily": { + "fails": "1 of 1 checks failed: InstalledFontCollection() returned Nothing" + }, + "Fx_FontCollectionTests.PrivateFontCollection_Create_ReturnsAnEmptyCollection": { + "fails": "2 of 2 checks failed: Err.Number after PrivateFontCollection() (Object doesn't support this property or method): expected 0, got 438; PrivateFontCollection() returns an object: expected False, got True" + }, + "Fx_FontCollectionTests.PrivateFontCollection_New_ReturnsACollection": { + "fails": "2 of 2 checks failed: Err.Number after New PrivateFontCollection (Object doesn't support this property or method): expected 0, got 438; New PrivateFontCollection returns an object: expected False, got True" + }, + "Fx_FontCollectionTests.PrivateFontCollection_AddFontFile_AddsTheFamilyOfTheFile": { + "fails": "1 of 1 checks failed: PrivateFontCollection() returned Nothing" + }, + "Fx_FontCollectionTests.PrivateFontCollection_AddFontFile_MissingFile_ReturnsFileNotFound": { + "fails": "1 of 1 checks failed: PrivateFontCollection() returned Nothing" + }, + "Fx_FontCollectionTests.PrivateFontCollection_FontFromTheCollection_IsAvailable": { + "fails": "1 of 1 checks failed: PrivateFontCollection() returned Nothing" + }, + "Fx_FontTests.FamilyName_OfAnInstalledFamily_EqualsTheNameGiven": { + "fails": "1 of 2 checks failed: FontFamily(\"Arial\").FamilyName: expected \"Arial\", got \"Arial" + }, + "Fx_FontTests.FamilyName_OfAGenericFamily_HasNoNulCharacter": { + "fails": "1 of 2 checks failed: position of a NUL character in GenericSansSerif().FamilyName: expected 0, got 21" + }, + "Fx_FontTests.Font_VariableDeclaredAsFont_HoldsAGdiPlusFont": { + "fails": "1 of 1 checks failed: Err.Number after assigning a GDI+ font to a variable declared As Font (No such interface supported): expected 0, got -2147467262" + }, + "Fx_StringFormatTests.Alignment_GetAfterSettingCenter_ReturnsCenter": { + "fails": "1 of 1 checks failed: Alignment after setting StringAlignmentCenter: expected 1, got 0" + }, + "Fx_StringFormatTests.Alignment_GetAfterSettingFar_ReturnsFar": { + "fails": "1 of 1 checks failed: Alignment after SetAlignmentFar: expected 2, got 0" + }, + "Fx_StringFormatTests.Alignment_Get_LeavesLastResultOk": { + "fails": "1 of 1 checks failed: LastResult after reading Alignment: expected 0, got 2" + }, + "Fx_StringFormatTests.IsAlignmentCenter_AfterSettingCenter_IsTrue": { + "fails": "2 of 3 checks failed: IsAlignmentCenter after SetAlignmentCenter: expected True, got False; IsAlignmentNear after SetAlignmentCenter: expected False, got True" + }, + "Fx_StringFormatTests.IsAlignmentFar_AfterSettingFar_IsTrue": { + "fails": "2 of 2 checks failed: IsAlignmentFar after setting StringAlignmentFar: expected True, got False; IsAlignmentNear after setting StringAlignmentFar: expected False, got True" + }, + "Fx_StringFormatTests.GetTabStops_FirstForm_ReturnsTheFirstTabOffset": { + "fails": "1 of 1 checks failed: the first tab offset returned by GetTabStops(firstTabOffset): expected 5, got 0" + }, + "Fx_StringFormatTests.SetDigitSubstitutionUser_KeepsTheLanguage": { + "fails": "1 of 2 checks failed: language after SetDigitSubstitutionUser: expected 1031, got 0" + }, + "Fx_StringFormatTests.SetDigitSubstitutionNone_KeepsTheLanguage": { + "fails": "1 of 2 checks failed: language after SetDigitSubstitutionNone: expected 1031, got 0" + }, + "Fx_StringFormatTests.SetDigitSubstitutionNational_KeepsTheLanguage": { + "fails": "1 of 2 checks failed: language after SetDigitSubstitutionNational: expected 1031, got 0" + }, + "Fx_StringFormatTests.SetDigitSubstitutionTraditional_KeepsTheLanguage": { + "fails": "1 of 2 checks failed: language after SetDigitSubstitutionTraditional: expected 1031, got 0" + }, + "Fx_StringFormatTests.DigitSubstitutionLanguage_Let_KeepsTheMethod": { + "fails": "1 of 2 checks failed: method after setting DigitSubstitutionLanguage: expected 2, got 0" + }, + "Ty_CColorTests.DarkSeaGreen_HasTheCssValue": { + "fails": "1 of 1 checks failed: CColor.DarkSeaGreen: expected \"FF8FBC8F\", got \"FF8FBC8B\"" + }, + "Ty_CColorTests.SolidBrush_TransparentMemberDrawsNothing": { + "fails": "1 of 1 checks failed: pixel of a black bitmap after FillRectangle with SolidBrush(CColor.Transparent): expected \"FF000000\", got \"FFFFFFFF\"" + }, + "Ty_CColorTests.Pen_TransparentMemberDrawsNothing": { + "fails": "1 of 1 checks failed: pixel of a black bitmap on the line of Pen(CColor.Transparent): expected \"FF000000\", got \"FFFFFFFF\"" + }, + "Ty_CColorTests.Clear_TransparentMemberClearsToTransparent": { + "fails": "1 of 1 checks failed: alpha of the pixel after Graphics.Clear CColor.Transparent: expected 0, got 255" + }, + "Ty_ColorTests.AsCOLORREF_ReturnsAColorref": { + "fails": "2 of 2 checks failed: AsCOLORREF of Color(&H22, &H33, &H44) (a COLORREF is &H00BBGGRR): expected \"00443322\", got \"00223344\"; AsCOLORREF of Color(&H22, &H33, &H44) against VBA.RGB(&H22, &H33, &H44): expected \"00443322\", got \"00223344\"" + }, + "Ty_ColorTests.FromCOLORREF_ReadsAColorref": { + "fails": "2 of 2 checks failed: Value of FromCOLORREF(RGB(&H22, &H33, &H44)): expected \"FF223344\", got \"FF443322\"; Value of FromCOLORREF(vbRed): expected \"FFFF0000\", got \"FF0000FF\"" + }, + "Ty_ColorTests.SetFromCOLORREF_ReadsAColorref": { + "fails": "2 of 2 checks failed: Value after SetFromCOLORREF RGB(&H22, &H33, &H44): expected \"FF223344\", got \"FF443322\"; Value after SetFromCOLORREF vbRed: expected \"FFFF0000\", got \"FF0000FF\"" + }, + "Ty_ColorTests.RotatedHue_KeepsTheAlpha": { + "fails": "1 of 1 checks failed: Value of Color(255, 0, 0).RotatedHue(120): expected \"FF00FF00\", got \"0000FF00\"" + }, + "Ty_ColorTests.RotateHue_KeepsTheAlpha": { + "fails": "1 of 1 checks failed: Value of Color(255, 0, 0) after RotateHue 120: expected \"FF00FF00\", got \"0000FF00\"" + }, + "Ty_GpPointTests.DotProd_ProductAboveTheRangeOfALongIsReturned": { + "fails": "2 of 2 checks failed: Err.Number after DotProd of (50000, 0) and (50000, 0): expected 0, got 6; DotProd of (50000, 0) and (50000, 0): expected 2500000000, got 0" + }, + "Ty_GpRectFTests.GpRectFFrom_ReadsTheLongEdgesStructure": { + "fails": "1 of 1 checks failed: GpRectFFrom of a GDIP_RECT with the edges 1, 2, 11, 22: expected \"1,2,10,20\", got \"0,0,0,0\"" + }, + "Ty_GpSizeFTests.GpSizeFFrom_ReadsTheSizeStructure": { + "fails": "1 of 1 checks failed: GpSizeFFrom of a GDIP_SIZE of 9 by 10: expected \"9,10\", got \"0,0\"" + }, + "Ty_GpSizeTests.GpSizeFrom_ReadsTheSizeStructure": { + "fails": "1 of 1 checks failed: GpSizeFrom of a GDIP_SIZE of 9 by 10: expected \"9,10\", got \"0,0\"" + }, + "Ty_StartupTests.Function_StartupParametersFormGivesVersionTwo": { + "win32": { + "fails": "2 of 2 checks failed: GdiplusVersion of GdiplusStartupInput(GdiplusStartupNoSetRound): expected 2, got 1; StartupParameters of GdiplusStartupInput(GdiplusStartupNoSetRound): expected 1, got 0" + }, + "win64": "pass" + }, + "Ty_StartupTests.Function_VersionFormGivesThatVersion": { + "win32": { + "fails": "2 of 2 checks failed: GdiplusVersion of GdiplusStartupInput(3, GdiplusStartupDefault): expected 3, got 2; StartupParameters of GdiplusStartupInput(3, GdiplusStartupDefault): expected 0, got 3" + }, + "win64": "pass" + }, + "Ty_StartupTests.ModuleFunctions_HookAndUnhookWithALongPtrToken": { + "fails": "2 of 2 checks failed: the status of the module function NotificationHook(token) after GDI+ was started with the output: expected 0, got 1; the token that the module function NotificationHook returned: expected True, got False" + }, + "Ty_StartupTests.NotificationHookProc_DoesNotWriteBeyondTheToken": { + "win32": "pass", + "win64": { + "fails": "1 of 2 checks failed: the four bytes after the Long token variable after NotificationHookProc wrote the token: expected \"12345678\", got \"00000000\"" + } + }, + "Im_AttributeTests.ColorKey_LongFormTakesAVbColour": { + "fails": "1 of 1 checks failed: the pixel of the colour 200, 100, 50 after SetColorKey with RGB(200, 100, 50) twice (black means keyed out; the value is a VB colour): expected -16777216, got -3644366" + }, + "Im_BitmapTests.New_TwoIntegerArgumentsMakeABitmap": { + "fails": "3 of 3 checks failed: LastResult of Bitmap(8, 6) with two Integer arguments (0 is Ok, 7 is Win32Error): expected 0, got 7; Width of Bitmap(8, 6): expected 8, got 0; Height of Bitmap(8, 6): expected 6, got 0" + }, + "Im_BitmapTests.New_NewKeywordWithTwoIntegerArguments": { + "fails": "2 of 2 checks failed: LastResult of New Bitmap(8, 6) with two Integer arguments (0 is Ok, 7 is Win32Error): expected 0, got 7; Width of New Bitmap(8, 6): expected 8, got 0" + }, + "Im_BitmapTests.ConvertFormat_FixedBWWithABlankPalette": { + "fails": "1 of 3 checks failed: the white pixel after the conversion with PaletteTypeFixedBW and a blank palette: expected -1, got -16777216" + }, + "Im_BitmapTests.ConvertFormat_OptimalWithABlankPalette": { + "fails": "1 of 2 checks failed: the pixel of a bitmap of one colour after the conversion with PaletteTypeOptimal and a blank palette: expected -3644366, got 0" + }, + "Im_CodecTests.GetImageEncoders_ReturnsTheInstalledEncoders": { + "hangs": true + }, + "Im_CodecTests.GetImageDecoders_ReturnsTheInstalledDecoders": { + "hangs": true + }, + "Im_CodecTests.Text_PropertiesHaveNoTerminatingNull": { + "fails": "4 of 4 checks failed: FormatDescription of the PNG encoder equals \"PNG\" (its length is 4): expected True, got False; MimeType of the PNG encoder equals \"image/png\" (its length is 10): expected True, got False; FilenameExtension of the PNG encoder equals \"*.PNG\" (its length is 6): expected True, got False; CodecName of the PNG encoder equals \"Built-in PNG Codec\" (its length is 19): expected True, got False" + }, + "Im_ImageTests.Save_EmptyParameterArrayWritesFile": { + "win32": { + "fails": "(runtime error -2147467259: NATIVE EXCEPTION: ACCESS_VIOLATION)" + }, + "win64": { + "fails": "2 of 2 checks failed: Save of a PNG with the one-element parameter array (no parameters) returns (0 is Ok): expected 0, got 2; Save of a PNG with the one-element parameter array writes a file of more than 0 bytes (its length is 0): expected True, got False" + } + }, + "Im_ImageTests.Save_JpegQualityChangesFileSize": { + "win32": { + "fails": "(runtime error -2147467259: NATIVE EXCEPTION: ACCESS_VIOLATION)" + }, + "win64": { + "fails": "1 of 4 checks failed: a JPEG saved with a quality of 10 is smaller than one saved with 100 (sizes 2910 and 2910): expected True, got False" + } + }, + "Im_ImageTests.Save_ParameterArrayStartingAtOneIsRefused": { + "win32": { + "fails": "(runtime error -2147467259: NATIVE EXCEPTION: ACCESS_VIOLATION)" + }, + "win64": "pass" + }, + "Im_ImageTests.SaveAdd_MultiFrameTiffHasTwoFrames": { + "win32": { + "fails": "(runtime error -2147467259: NATIVE EXCEPTION: ACCESS_VIOLATION)" + }, + "win64": { + "fails": "3 of 4 checks failed: SaveAddImage of the second frame returns (0 is Ok): expected 0, got 7; SaveAdd that flushes the file returns (0 is Ok): expected 0, got 7; the page count of the TIFF written with Save, SaveAddImage and SaveAdd: expected 2, got 1" + } + }, + "Im_ImageTests.Palette_LetChangesThePicture": { + "fails": "2 of 2 checks failed: setting Palette of an 8-bit indexed bitmap leaves LastResult (0 is Ok): expected 0, got 2; the colour of a pixel of an 8-bit indexed bitmap after Palette is set to one colour (it was FF000000): expected -15654349, got -16777216" + }, + "Im_ImageTests.PropertyItem_PropertyOfTheReturnedElement": { + "fails": "(runtime error -2147467259: NATIVE EXCEPTION: ACCESS_VIOLATION)" + }, + "Im_ImageTests.GetEncoderParameterList_JpegListsTheQuality": { + "fails": "(runtime error -2147467259: NATIVE EXCEPTION: ACCESS_VIOLATION)" + }, + "Im_MetafileTests.New_FromFileName": { + "fails": "1 of 2 checks failed: Err.Number of Metafile(filename) (Type mismatch): expected 0, got 13" + }, + "Im_MetafileTests.New_RecordingToFileNameWithReferenceDc": { + "fails": "1 of 1 checks failed: Err.Number of Metafile(filename, referenceHdc) (Type mismatch): expected 0, got 13" + }, + "Im_MetafileTests.New_RecordingWithReferenceDcAndType": { + "fails": "1 of 1 checks failed: Err.Number of Metafile(referenceHdc, EmfTypeEmfPlusOnly) (Type mismatch): expected 0, got 13" + }, + "Im_MetafileTests.New_ReferenceDcAloneRecords": { + "fails": "1 of 1 checks failed: LastResult of Metafile(referenceHdc) (0 is Ok, 1 is GenericError): expected 0, got 1" + }, + "Im_MetafileTests.ConvertToEmfPlus_WithoutFileNameOrStream": { + "fails": "1 of 1 checks failed: Err.Number of ConvertToEmfPlus(g) (Type mismatch): expected 0, got 13" + }, + "Im_TypeTests.GpPropertyItem_TheSByteReadsASignedByte": { + "fails": "1 of 2 checks failed: TheSByte of an item of the type signed byte that holds 5: expected 5, got 0" + }, + "Im_TypeTests.GpPropertyItem_TheSShortReadsASignedShort": { + "fails": "1 of 2 checks failed: TheSShort of an item of the type signed short that holds -300: expected -300, got 0" + }, + "Im_TypeTests.GpPropertyItem_TheSLongReadsASignedLong": { + "fails": "1 of 2 checks failed: TheSLong of an item of the type signed long that holds -100000: expected -100000, got 0" + }, + "Im_TypeTests.GpPropertyItem_TheDoubleReadsADouble": { + "fails": "1 of 2 checks failed: TheDouble of an item of the type double that holds 2.5: expected 2.5, got 0" + }, + "Im_TypeTests.GpPropertyItem_AsDoubleCoversTheSignedAndDoubleTypes": { + "fails": "4 of 4 checks failed: AsDouble of an item of the type signed byte that holds 5: expected 5, got 0; AsDouble of an item of the type signed short that holds -300: expected -300, got 0; AsDouble of an item of the type signed long that holds -100000: expected -100000, got 0; AsDouble of an item of the type double that holds 2.5: expected 2.5, got 0" + }, + "Im_TypeTests.GpPropertyItem_GetSRationalReadsASignedRational": { + "fails": "2 of 2 checks failed: the numerator that GetSRational reads from an item that holds -7/2: expected -7, got 0; the denominator that GetSRational reads from an item that holds -7/2: expected 2, got 0" + }, + "Im_TypeTests.ColorPalette_FunctionCopiesTheColours": { + "fails": "2 of 4 checks failed: entries(0) of ColorPalette(PaletteFlagsGrayScale, colours): expected -16777216, got 0; entries(1) of ColorPalette(PaletteFlagsGrayScale, colours): expected -1, got 0" + }, + "Im_ZStackTests.Palette_GetReturnsThePalette": { + "fails": "(runtime error -2147467259: NATIVE EXCEPTION: STACK_OVERFLOW)" + } + } +} From 5bad4a7946f6498cb6c4647a78d2862eef120a9a Mon Sep 17 00:00:00 2001 From: Kuba Sunderland-Ober Date: Fri, 16 Oct 2026 12:18:53 +0200 Subject: [PATCH 02/49] BUGS-TO-REPORT: 21 filed entries moved to bugs/filed/ with their reports (bug_repro file --marked) --- BUGS-TO-REPORT.md | 1713 +---------------- .../activex-control-class-variable/REPORT.md | 62 + .../activex-control-class-variable.twinproj | Bin .../activex-control-class-variable/repro.json | 3 +- .../src/Settings | 0 .../src/Sources/Startup.twin | 0 .../vb6/Holder.cls | 0 .../vb6/Module1.bas | 0 .../vb6/Probe.vbp | 0 .../REPORT.md | 269 +++ ...ay-field-element-assignment-crash.twinproj | Bin .../repro.json | 3 +- .../src/Settings | 0 .../src/Sources/Startup.twin | 0 .../vb6/Holder.cls | 0 .../vb6/Module1.bas | 0 .../vb6/Probe.vbp | 0 .../REPORT.md | 269 +++ ...ur-validation-data-ole-multiframe.twinproj | Bin .../repro.json | 3 +- .../src/Settings | 0 .../src/Sources/ColourPropertyTests.twin | 0 .../src/Sources/Form1.tbform | 0 .../src/Sources/Form1.twin | 0 .../src/Sources/MyMDIForm.tbform | 0 .../src/Sources/MyMDIForm.twin | 0 .../src/Sources/Startup.twin | 0 .../src/Sources/UC1.tbcontrol | 0 .../src/Sources/UC1.twin | 0 .../vb6/Form1.frm | 0 .../vb6/MDIForm1.frm | 0 .../vb6/Module1.bas | 0 .../vb6/Probe.vbp | 0 .../vb6/UC1.ctl | 0 .../constantfoldable-declare-crash/REPORT.md | 43 + .../constantfoldable-declare-crash.twinproj | Bin .../repro.json | 3 +- .../src/Settings | 0 .../src/Sources/Startup.twin | 0 .../dateadd-month-end-overflow/REPORT.md | 59 + .../dateadd-month-end-overflow.twinproj | Bin .../dateadd-month-end-overflow/repro.json | 3 +- .../dateadd-month-end-overflow/src/Settings | 0 .../src/Sources/Startup.twin | 0 .../vb6/Module1.bas | 0 .../dateadd-month-end-overflow/vb6/Probe.vbp | 0 bugs/filed/gosub-llvm-exe-crash/REPORT.md | 52 + .../gosub-llvm-exe-crash.twinproj | Bin .../gosub-llvm-exe-crash/repro.json | 7 +- .../gosub-llvm-exe-crash/src/Settings | 0 .../src/Sources/Startup.twin | 0 bugs/filed/keyboard-group-bar-name/REPORT.md | 27 + .../keyboard-group-bar-name/repro.json | 3 +- .../REPORT.md | 101 + ...istitem-backcolor-default-refused.twinproj | Bin .../repro.json | 3 +- .../src/Settings | 0 .../src/Sources/Form1.tbform | 0 .../src/Sources/Form1.twin | 0 .../src/Sources/ListItemBackColorTests.twin | 0 .../src/Sources/Startup.twin | 0 .../llvm-byval-to-byref-sub-codegen/REPORT.md | 48 + .../llvm-byval-to-byref-sub-codegen.twinproj | Bin .../repro.json | 8 +- .../src/Settings | 0 .../src/Sources/Startup.twin | 0 .../REPORT.md | 49 + ...vm64-byval-to-byref-declare-crash.twinproj | Bin .../repro.json | 8 +- .../src/Settings | 0 .../src/Sources/Startup.twin | 0 bugs/filed/paramarray-variant-array/REPORT.md | 48 + .../paramarray-variant-array.twinproj | Bin .../paramarray-variant-array/repro.json | 3 +- .../paramarray-variant-array/src/Settings | 0 .../src/Sources/Cases.twin | 0 .../src/Sources/ParamArrayTests.twin | 0 .../src/Sources/Startup.twin | 0 .../src/Sources/Widget.twin | 0 .../paramarray-variant-array/vb6/Module1.bas | 0 .../paramarray-variant-array/vb6/Probe.vbp | 0 .../paramarray-variant-array/vb6/Widget.cls | 0 .../REPORT.md | 52 + ...dim-empty-range-unspecified-error.twinproj | Bin .../repro.json | 3 +- .../src/Settings | 0 .../src/Sources/Startup.twin | 0 .../vb6/Module1.bas | 0 .../vb6/Probe.vbp | 0 .../REPORT.md | 41 + .../repro.json | 7 +- ...afterbuild-generic-sub-never-runs.twinproj | Bin .../src/Settings | 0 .../src/Sources/Startup.twin | 0 .../REPORT.md | 41 + .../repro.json | 3 +- ...beforestartupobject-generic-crash.twinproj | Bin .../src/Settings | 0 .../src/Sources/Startup.twin | 0 .../REPORT.md | 55 + .../repro.json | 3 +- ...ipt-control-reference-win64-crash.twinproj | Bin .../src/Settings | 0 .../src/Sources/Startup.twin | 0 .../slider-backcolor-no-repaint/REPORT.md | 40 + .../slider-backcolor-no-repaint/repro.json | 3 +- .../slider-backcolor-no-repaint.twinproj | Bin .../slider-backcolor-no-repaint/src/Settings | 0 .../src/Sources/Form1.tbform | 0 .../src/Sources/Form1.twin | 0 .../src/Sources/Startup.twin | 0 bugs/filed/theme-inherit-loop/REPORT.md | 39 + .../{ => filed}/theme-inherit-loop/repro.json | 3 +- bugs/filed/theme-missing-semicolon/REPORT.md | 34 + .../theme-missing-semicolon/repro.json | 7 +- .../REPORT.md | 35 + .../repro.json | 3 +- .../vb6-attribute-lines-no-effect/REPORT.md | 26 + .../vb6-attribute-lines-no-effect/repro.json | 3 +- .../src/Settings | 0 .../src/Sources/Attrs.cls | 0 .../src/Sources/TwinAttrs.twin | 0 .../vb6-attribute-lines-no-effect.twinproj | Bin .../REPORT.md | 180 ++ .../repro.json | 3 +- .../src/Settings | 0 .../src/Sources/Form1.tbform | 0 .../src/Sources/Form1.twin | 0 .../src/Sources/HeaderEnumerationTests.twin | 0 .../src/Sources/Startup.twin | 0 ...iew2-response-headers-reset-clone.twinproj | Bin 131 files changed, 1690 insertions(+), 1678 deletions(-) create mode 100644 bugs/filed/activex-control-class-variable/REPORT.md rename bugs/{ => filed}/activex-control-class-variable/activex-control-class-variable.twinproj (100%) rename bugs/{ => filed}/activex-control-class-variable/repro.json (98%) rename bugs/{ => filed}/activex-control-class-variable/src/Settings (100%) rename bugs/{ => filed}/activex-control-class-variable/src/Sources/Startup.twin (100%) rename bugs/{ => filed}/activex-control-class-variable/vb6/Holder.cls (100%) rename bugs/{ => filed}/activex-control-class-variable/vb6/Module1.bas (100%) rename bugs/{ => filed}/activex-control-class-variable/vb6/Probe.vbp (100%) create mode 100644 bugs/filed/class-array-field-element-assignment-crash/REPORT.md rename bugs/{ => filed}/class-array-field-element-assignment-crash/class-array-field-element-assignment-crash.twinproj (100%) rename bugs/{ => filed}/class-array-field-element-assignment-crash/repro.json (71%) rename bugs/{ => filed}/class-array-field-element-assignment-crash/src/Settings (100%) rename bugs/{ => filed}/class-array-field-element-assignment-crash/src/Sources/Startup.twin (100%) rename bugs/{ => filed}/class-array-field-element-assignment-crash/vb6/Holder.cls (100%) rename bugs/{ => filed}/class-array-field-element-assignment-crash/vb6/Module1.bas (100%) rename bugs/{ => filed}/class-array-field-element-assignment-crash/vb6/Probe.vbp (100%) create mode 100644 bugs/filed/colour-validation-data-ole-multiframe/REPORT.md rename bugs/{ => filed}/colour-validation-data-ole-multiframe/colour-validation-data-ole-multiframe.twinproj (100%) rename bugs/{ => filed}/colour-validation-data-ole-multiframe/repro.json (98%) rename bugs/{ => filed}/colour-validation-data-ole-multiframe/src/Settings (100%) rename bugs/{ => filed}/colour-validation-data-ole-multiframe/src/Sources/ColourPropertyTests.twin (100%) rename bugs/{ => filed}/colour-validation-data-ole-multiframe/src/Sources/Form1.tbform (100%) rename bugs/{ => filed}/colour-validation-data-ole-multiframe/src/Sources/Form1.twin (100%) rename bugs/{ => filed}/colour-validation-data-ole-multiframe/src/Sources/MyMDIForm.tbform (100%) rename bugs/{ => filed}/colour-validation-data-ole-multiframe/src/Sources/MyMDIForm.twin (100%) rename bugs/{ => filed}/colour-validation-data-ole-multiframe/src/Sources/Startup.twin (100%) rename bugs/{ => filed}/colour-validation-data-ole-multiframe/src/Sources/UC1.tbcontrol (100%) rename bugs/{ => filed}/colour-validation-data-ole-multiframe/src/Sources/UC1.twin (100%) rename bugs/{ => filed}/colour-validation-data-ole-multiframe/vb6/Form1.frm (100%) rename bugs/{ => filed}/colour-validation-data-ole-multiframe/vb6/MDIForm1.frm (100%) rename bugs/{ => filed}/colour-validation-data-ole-multiframe/vb6/Module1.bas (100%) rename bugs/{ => filed}/colour-validation-data-ole-multiframe/vb6/Probe.vbp (100%) rename bugs/{ => filed}/colour-validation-data-ole-multiframe/vb6/UC1.ctl (100%) create mode 100644 bugs/filed/constantfoldable-declare-crash/REPORT.md rename bugs/{ => filed}/constantfoldable-declare-crash/constantfoldable-declare-crash.twinproj (100%) rename bugs/{runbeforestartupobject-generic-crash => filed/constantfoldable-declare-crash}/repro.json (80%) rename bugs/{ => filed}/constantfoldable-declare-crash/src/Settings (100%) rename bugs/{ => filed}/constantfoldable-declare-crash/src/Sources/Startup.twin (100%) create mode 100644 bugs/filed/dateadd-month-end-overflow/REPORT.md rename bugs/{ => filed}/dateadd-month-end-overflow/dateadd-month-end-overflow.twinproj (100%) rename bugs/{ => filed}/dateadd-month-end-overflow/repro.json (97%) rename bugs/{ => filed}/dateadd-month-end-overflow/src/Settings (100%) rename bugs/{ => filed}/dateadd-month-end-overflow/src/Sources/Startup.twin (100%) rename bugs/{ => filed}/dateadd-month-end-overflow/vb6/Module1.bas (100%) rename bugs/{ => filed}/dateadd-month-end-overflow/vb6/Probe.vbp (100%) create mode 100644 bugs/filed/gosub-llvm-exe-crash/REPORT.md rename bugs/{ => filed}/gosub-llvm-exe-crash/gosub-llvm-exe-crash.twinproj (100%) rename bugs/{ => filed}/gosub-llvm-exe-crash/repro.json (71%) rename bugs/{ => filed}/gosub-llvm-exe-crash/src/Settings (100%) rename bugs/{ => filed}/gosub-llvm-exe-crash/src/Sources/Startup.twin (100%) create mode 100644 bugs/filed/keyboard-group-bar-name/REPORT.md rename bugs/{ => filed}/keyboard-group-bar-name/repro.json (96%) create mode 100644 bugs/filed/listitem-backcolor-default-refused/REPORT.md rename bugs/{ => filed}/listitem-backcolor-default-refused/listitem-backcolor-default-refused.twinproj (100%) rename bugs/{ => filed}/listitem-backcolor-default-refused/repro.json (95%) rename bugs/{ => filed}/listitem-backcolor-default-refused/src/Settings (100%) rename bugs/{ => filed}/listitem-backcolor-default-refused/src/Sources/Form1.tbform (100%) rename bugs/{ => filed}/listitem-backcolor-default-refused/src/Sources/Form1.twin (100%) rename bugs/{ => filed}/listitem-backcolor-default-refused/src/Sources/ListItemBackColorTests.twin (100%) rename bugs/{ => filed}/listitem-backcolor-default-refused/src/Sources/Startup.twin (100%) create mode 100644 bugs/filed/llvm-byval-to-byref-sub-codegen/REPORT.md rename bugs/{ => filed}/llvm-byval-to-byref-sub-codegen/llvm-byval-to-byref-sub-codegen.twinproj (100%) rename bugs/{ => filed}/llvm-byval-to-byref-sub-codegen/repro.json (77%) rename bugs/{ => filed}/llvm-byval-to-byref-sub-codegen/src/Settings (100%) rename bugs/{ => filed}/llvm-byval-to-byref-sub-codegen/src/Sources/Startup.twin (100%) create mode 100644 bugs/filed/llvm64-byval-to-byref-declare-crash/REPORT.md rename bugs/{ => filed}/llvm64-byval-to-byref-declare-crash/llvm64-byval-to-byref-declare-crash.twinproj (100%) rename bugs/{ => filed}/llvm64-byval-to-byref-declare-crash/repro.json (72%) rename bugs/{ => filed}/llvm64-byval-to-byref-declare-crash/src/Settings (100%) rename bugs/{ => filed}/llvm64-byval-to-byref-declare-crash/src/Sources/Startup.twin (100%) create mode 100644 bugs/filed/paramarray-variant-array/REPORT.md rename bugs/{ => filed}/paramarray-variant-array/paramarray-variant-array.twinproj (100%) rename bugs/{ => filed}/paramarray-variant-array/repro.json (99%) rename bugs/{ => filed}/paramarray-variant-array/src/Settings (100%) rename bugs/{ => filed}/paramarray-variant-array/src/Sources/Cases.twin (100%) rename bugs/{ => filed}/paramarray-variant-array/src/Sources/ParamArrayTests.twin (100%) rename bugs/{ => filed}/paramarray-variant-array/src/Sources/Startup.twin (100%) rename bugs/{ => filed}/paramarray-variant-array/src/Sources/Widget.twin (100%) rename bugs/{ => filed}/paramarray-variant-array/vb6/Module1.bas (100%) rename bugs/{ => filed}/paramarray-variant-array/vb6/Probe.vbp (100%) rename bugs/{ => filed}/paramarray-variant-array/vb6/Widget.cls (100%) create mode 100644 bugs/filed/redim-empty-range-unspecified-error/REPORT.md rename bugs/{ => filed}/redim-empty-range-unspecified-error/redim-empty-range-unspecified-error.twinproj (100%) rename bugs/{ => filed}/redim-empty-range-unspecified-error/repro.json (98%) rename bugs/{ => filed}/redim-empty-range-unspecified-error/src/Settings (100%) rename bugs/{ => filed}/redim-empty-range-unspecified-error/src/Sources/Startup.twin (100%) rename bugs/{ => filed}/redim-empty-range-unspecified-error/vb6/Module1.bas (100%) rename bugs/{ => filed}/redim-empty-range-unspecified-error/vb6/Probe.vbp (100%) create mode 100644 bugs/filed/runafterbuild-generic-sub-never-runs/REPORT.md rename bugs/{ => filed}/runafterbuild-generic-sub-never-runs/repro.json (77%) rename bugs/{ => filed}/runafterbuild-generic-sub-never-runs/runafterbuild-generic-sub-never-runs.twinproj (100%) rename bugs/{ => filed}/runafterbuild-generic-sub-never-runs/src/Settings (100%) rename bugs/{ => filed}/runafterbuild-generic-sub-never-runs/src/Sources/Startup.twin (100%) create mode 100644 bugs/filed/runbeforestartupobject-generic-crash/REPORT.md rename bugs/{constantfoldable-declare-crash => filed/runbeforestartupobject-generic-crash}/repro.json (80%) rename bugs/{ => filed}/runbeforestartupobject-generic-crash/runbeforestartupobject-generic-crash.twinproj (100%) rename bugs/{ => filed}/runbeforestartupobject-generic-crash/src/Settings (100%) rename bugs/{ => filed}/runbeforestartupobject-generic-crash/src/Sources/Startup.twin (100%) create mode 100644 bugs/filed/script-control-reference-win64-crash/REPORT.md rename bugs/{ => filed}/script-control-reference-win64-crash/repro.json (77%) rename bugs/{ => filed}/script-control-reference-win64-crash/script-control-reference-win64-crash.twinproj (100%) rename bugs/{ => filed}/script-control-reference-win64-crash/src/Settings (100%) rename bugs/{ => filed}/script-control-reference-win64-crash/src/Sources/Startup.twin (100%) create mode 100644 bugs/filed/slider-backcolor-no-repaint/REPORT.md rename bugs/{ => filed}/slider-backcolor-no-repaint/repro.json (93%) rename bugs/{ => filed}/slider-backcolor-no-repaint/slider-backcolor-no-repaint.twinproj (100%) rename bugs/{ => filed}/slider-backcolor-no-repaint/src/Settings (100%) rename bugs/{ => filed}/slider-backcolor-no-repaint/src/Sources/Form1.tbform (100%) rename bugs/{ => filed}/slider-backcolor-no-repaint/src/Sources/Form1.twin (100%) rename bugs/{ => filed}/slider-backcolor-no-repaint/src/Sources/Startup.twin (100%) create mode 100644 bugs/filed/theme-inherit-loop/REPORT.md rename bugs/{ => filed}/theme-inherit-loop/repro.json (96%) create mode 100644 bugs/filed/theme-missing-semicolon/REPORT.md rename bugs/{ => filed}/theme-missing-semicolon/repro.json (94%) create mode 100644 bugs/filed/theme-override-breaks-inheritance/REPORT.md rename bugs/{ => filed}/theme-override-breaks-inheritance/repro.json (93%) create mode 100644 bugs/filed/vb6-attribute-lines-no-effect/REPORT.md rename bugs/{ => filed}/vb6-attribute-lines-no-effect/repro.json (94%) rename bugs/{ => filed}/vb6-attribute-lines-no-effect/src/Settings (100%) rename bugs/{ => filed}/vb6-attribute-lines-no-effect/src/Sources/Attrs.cls (100%) rename bugs/{ => filed}/vb6-attribute-lines-no-effect/src/Sources/TwinAttrs.twin (100%) rename bugs/{ => filed}/vb6-attribute-lines-no-effect/vb6-attribute-lines-no-effect.twinproj (100%) create mode 100644 bugs/filed/webview2-response-headers-reset-clone/REPORT.md rename bugs/{ => filed}/webview2-response-headers-reset-clone/repro.json (96%) rename bugs/{ => filed}/webview2-response-headers-reset-clone/src/Settings (100%) rename bugs/{ => filed}/webview2-response-headers-reset-clone/src/Sources/Form1.tbform (100%) rename bugs/{ => filed}/webview2-response-headers-reset-clone/src/Sources/Form1.twin (100%) rename bugs/{ => filed}/webview2-response-headers-reset-clone/src/Sources/HeaderEnumerationTests.twin (100%) rename bugs/{ => filed}/webview2-response-headers-reset-clone/src/Sources/Startup.twin (100%) rename bugs/{ => filed}/webview2-response-headers-reset-clone/webview2-response-headers-reset-clone.twinproj (100%) diff --git a/BUGS-TO-REPORT.md b/BUGS-TO-REPORT.md index d6c2f19a..78ce0eb8 100644 --- a/BUGS-TO-REPORT.md +++ b/BUGS-TO-REPORT.md @@ -1945,457 +1945,6 @@ Severity: moderate. Code that uses an `Object` variable or `CallByName`, as code --- -## `Data.ForeColor`, `Data.BackColor`, `OLE.BackColor` and `MultiFrame.BackColor` store a value that is not a colour, where VB6 raises error 380 - -**Describe the bug** -Follow-up to #2484, which is fixed in BETA 1005 for every colour property of Form, PictureBox, UserControl, Printer, MDIForm, Label, TextBox, CommandButton and the other controls it named: each now raises error 380, *Invalid property value*, and keeps its colour, as VB6 does. This is the four properties that still store the value: `ForeColor` and `BackColor` of the Data control, `BackColor` of the OLE control and `BackColor` of the MultiFrame. Assigning `-1` to any of them raises no error, and the property then reads `FFFFFFFF`. Observed by running the reproducer's test cases. - -**To Reproduce** -Steps to reproduce the behavior: -1. Open `colour-validation-data-ole-multiframe.twinproj` (attached as `colour-validation-data-ole-multiframe.zip`). Its `Form1` holds a PictureBox, a Label, a TextBox, a CommandButton, a user control (`UC1`), a Data control, an OLE control and a MultiFrame; `MyMDIForm` is an MDIForm. -2. Run, with `On Error Resume Next`: - ``` - Load Form1 - Form1.Data1.ForeColor = vbGreen - Form1.Data1.ForeColor = -1 - Debug.Print Err.Number, Hex$(Form1.Data1.ForeColor) - ``` -3. See `0 FFFFFFFF`. The same with `Form1.Label1.ForeColor` prints `380 FF00`. - -The module below is the reproducer's, and is meant to be adapted as a regression test. It loads the forms and never shows them. On BETA 1005 the seventeen cases from #2484 pass, and the four cases for the Data control, the OLE control and the MultiFrame fail with `Expected: 380, Actual: 0`. -```vb -' Regression tests for #2484 and its follow-up. Assigning a value that is not a -' colour to a colour property raises error 380, Invalid property value, and -' leaves the colour it had: VB6 6.0 does this for each property it has (the VB6 -' project beside this one prints them), and twinBASIC does it for the other -' controls. Form1 holds one control of each class; it is loaded, never shown. -' Each case sets the colour to vbGreen, assigns the bad value early-bound under -' On Error Resume Next, and checks the error and the colour read back. -[TestFixture] -Module ColourPropertyTests - - Private Const NotAColour As Long = -1 - Private Const SystemColourOutOfRange As Long = &H8000001F - - Private Sub Expect380(ByVal Number As Long, ByVal After As Long) - Assert.Strict.AreEqual 380, Number, "the error" ' VB6: 380, Invalid property value - Assert.Strict.AreEqual CLng(vbGreen), After, "the colour it had" ' VB6: the old colour is kept - End Sub - - ' Fixed in #2484 (BETA 1005). - - [TestCase] - Public Sub Form_ForeColor() - Dim number As Long, after As Long - Load Form1: Form1.ForeColor = vbGreen - On Error Resume Next: Form1.ForeColor = NotAColour: number = Err.Number: On Error GoTo 0 - after = Form1.ForeColor: Unload Form1 - Expect380 number, after - End Sub - - [TestCase] - Public Sub Form_ForeColor_SystemColourOutOfRange() - Dim number As Long, after As Long - Load Form1: Form1.ForeColor = vbGreen - On Error Resume Next: Form1.ForeColor = SystemColourOutOfRange: number = Err.Number: On Error GoTo 0 - after = Form1.ForeColor: Unload Form1 - Expect380 number, after - End Sub - - [TestCase] - Public Sub Form_BackColor() - Dim number As Long, after As Long - Load Form1: Form1.BackColor = vbGreen - On Error Resume Next: Form1.BackColor = NotAColour: number = Err.Number: On Error GoTo 0 - after = Form1.BackColor: Unload Form1 - Expect380 number, after - End Sub - - [TestCase] - Public Sub Form_FillColor() - Dim number As Long, after As Long - Load Form1: Form1.FillColor = vbGreen - On Error Resume Next: Form1.FillColor = NotAColour: number = Err.Number: On Error GoTo 0 - after = Form1.FillColor: Unload Form1 - Expect380 number, after - End Sub - - [TestCase] - Public Sub PictureBox_ForeColor() - Dim number As Long, after As Long - Load Form1: Form1.PictureBox1.ForeColor = vbGreen - On Error Resume Next: Form1.PictureBox1.ForeColor = NotAColour: number = Err.Number: On Error GoTo 0 - after = Form1.PictureBox1.ForeColor: Unload Form1 - Expect380 number, after - End Sub - - [TestCase] - Public Sub PictureBox_BackColor() - Dim number As Long, after As Long - Load Form1: Form1.PictureBox1.BackColor = vbGreen - On Error Resume Next: Form1.PictureBox1.BackColor = NotAColour: number = Err.Number: On Error GoTo 0 - after = Form1.PictureBox1.BackColor: Unload Form1 - Expect380 number, after - End Sub - - [TestCase] - Public Sub PictureBox_FillColor() - Dim number As Long, after As Long - Load Form1: Form1.PictureBox1.FillColor = vbGreen - On Error Resume Next: Form1.PictureBox1.FillColor = NotAColour: number = Err.Number: On Error GoTo 0 - after = Form1.PictureBox1.FillColor: Unload Form1 - Expect380 number, after - End Sub - - [TestCase] - Public Sub Label_ForeColor() - Dim number As Long, after As Long - Load Form1: Form1.Label1.ForeColor = vbGreen - On Error Resume Next: Form1.Label1.ForeColor = NotAColour: number = Err.Number: On Error GoTo 0 - after = Form1.Label1.ForeColor: Unload Form1 - Expect380 number, after - End Sub - - [TestCase] - Public Sub Label_BackColor() - Dim number As Long, after As Long - Load Form1: Form1.Label1.BackColor = vbGreen - On Error Resume Next: Form1.Label1.BackColor = NotAColour: number = Err.Number: On Error GoTo 0 - after = Form1.Label1.BackColor: Unload Form1 - Expect380 number, after - End Sub - - [TestCase] - Public Sub TextBox_ForeColor() - Dim number As Long, after As Long - Load Form1: Form1.TextBox1.ForeColor = vbGreen - On Error Resume Next: Form1.TextBox1.ForeColor = NotAColour: number = Err.Number: On Error GoTo 0 - after = Form1.TextBox1.ForeColor: Unload Form1 - Expect380 number, after - End Sub - - [TestCase] - Public Sub TextBox_BackColor() - Dim number As Long, after As Long - Load Form1: Form1.TextBox1.BackColor = vbGreen - On Error Resume Next: Form1.TextBox1.BackColor = NotAColour: number = Err.Number: On Error GoTo 0 - after = Form1.TextBox1.BackColor: Unload Form1 - Expect380 number, after - End Sub - - [TestCase] - Public Sub CommandButton_BackColor() - Dim number As Long, after As Long - Load Form1: Form1.CommandButton1.BackColor = vbGreen - On Error Resume Next: Form1.CommandButton1.BackColor = NotAColour: number = Err.Number: On Error GoTo 0 - after = Form1.CommandButton1.BackColor: Unload Form1 - Expect380 number, after - End Sub - - [TestCase] - Public Sub UserControl_ForeColor() - Dim number As Long, after As Long - Load Form1: Form1.UC1.TrySet "ForeColor", NotAColour, number, after: Unload Form1 - Expect380 number, after - End Sub - - [TestCase] - Public Sub UserControl_BackColor() - Dim number As Long, after As Long - Load Form1: Form1.UC1.TrySet "BackColor", NotAColour, number, after: Unload Form1 - Expect380 number, after - End Sub - - [TestCase] - Public Sub UserControl_FillColor() - Dim number As Long, after As Long - Load Form1: Form1.UC1.TrySet "FillColor", NotAColour, number, after: Unload Form1 - Expect380 number, after - End Sub - - [TestCase] - Public Sub MDIForm_BackColor() - Dim number As Long, after As Long - Load MyMDIForm: MyMDIForm.BackColor = vbGreen - On Error Resume Next: MyMDIForm.BackColor = NotAColour: number = Err.Number: On Error GoTo 0 - after = MyMDIForm.BackColor: Unload MyMDIForm - Expect380 number, after - End Sub - - [TestCase] - Public Sub MDIForm_BackColor_SystemColourOutOfRange() - Dim number As Long, after As Long - Load MyMDIForm: MyMDIForm.BackColor = vbGreen - On Error Resume Next: MyMDIForm.BackColor = SystemColourOutOfRange: number = Err.Number: On Error GoTo 0 - after = MyMDIForm.BackColor: Unload MyMDIForm - Expect380 number, after - End Sub - - ' Left in BETA 1005: these store the value and raise no error. - - [TestCase] - Public Sub Data_ForeColor() - Dim number As Long, after As Long - Load Form1: Form1.Data1.ForeColor = vbGreen - On Error Resume Next: Form1.Data1.ForeColor = NotAColour: number = Err.Number: On Error GoTo 0 - after = Form1.Data1.ForeColor: Unload Form1 - Expect380 number, after - End Sub - - [TestCase] - Public Sub Data_BackColor() - Dim number As Long, after As Long - Load Form1: Form1.Data1.BackColor = vbGreen - On Error Resume Next: Form1.Data1.BackColor = NotAColour: number = Err.Number: On Error GoTo 0 - after = Form1.Data1.BackColor: Unload Form1 - Expect380 number, after - End Sub - - [TestCase] - Public Sub OLE_BackColor() - Dim number As Long, after As Long - Load Form1: Form1.OLE1.BackColor = vbGreen - On Error Resume Next: Form1.OLE1.BackColor = NotAColour: number = Err.Number: On Error GoTo 0 - after = Form1.OLE1.BackColor: Unload Form1 - Expect380 number, after - End Sub - - [TestCase] - Public Sub MultiFrame_BackColor() - Dim number As Long, after As Long - Load Form1: Form1.MultiFrame1.BackColor = vbGreen - On Error Resume Next: Form1.MultiFrame1.BackColor = NotAColour: number = Err.Number: On Error GoTo 0 - after = Form1.MultiFrame1.BackColor: Unload Form1 - Expect380 number, after - End Sub - -End Module -``` -`UC1`, the user control on `Form1`, sets its own colour properties for the three `UserControl_` cases: -```vb - ' Sets one of the user control's own colour properties to vbGreen, then to - ' Value; returns the error the second assignment raised, and the colour after it. - Public Sub TrySet(ByVal Prop As String, ByVal Value As Long, ByRef Number As Long, ByRef After As Long) - Select Case Prop - Case "ForeColor": ForeColor = vbGreen - Case "BackColor": BackColor = vbGreen - Case "FillColor": FillColor = vbGreen - End Select - On Error Resume Next - Select Case Prop - Case "ForeColor": ForeColor = Value - Case "BackColor": BackColor = Value - Case "FillColor": FillColor = Value - End Select - Number = Err.Number - On Error GoTo 0 - Select Case Prop - Case "ForeColor": After = ForeColor - Case "BackColor": After = BackColor - Case "FillColor": After = FillColor - End Select - End Sub -``` - -**Expected behavior** -Error 380 for each assignment, and the old colour kept, as twinBASIC now does for the other controls, and as VB6 does for `OLE.BackColor`. The VB6 project, attached as `colour-validation-data-ole-multiframe-vb6.zip`, prints `OLE.BackColor = -1: Err 380, reads FF00`, and 380 for each of the other properties of the test. It has no Data control (none could be loaded in VB6 on the machine), and VB6 has no MultiFrame. - -**Desktop:** - - OS: Windows 10 Pro 22H2 (build 19045) - - twinBASIC compiler version: BETA 1005 - -**Additional context** -`&H8000001F` and `&HFF0000FF`, which are not colours either, are stored by the same four properties with no error. In the BETA 1005 package source, the Data control, the OLE control and the MultiFrame declare these as plain `Public ... As OLE_COLOR` fields, with no `OnPropertyLet` handler that calls `CommonValidateColorOrRevert`, unlike the other controls. `OLE.BackColor` also gets the compile warning TB0009, *[BackColor] has not yet been implemented*, wherever it is used, and stores the value all the same. - -Not tested here: `Printer.ForeColor` and `Printer.FillColor`, which #2484 named, need a default printer; on BETA 1005 they raise 380 and keep the colour when there is one (#2484's reproducer prints them). - -Severity: low. The stored value is what the control then paints with, and no error tells the program that the colour it was given is not one. - - - ---- - -## After `Reset`, `Clone` of the WebView2 response headers enumerator skips the headers read before the `Reset` - -**Describe the bug** -Follow-up to #2496, which is fixed in BETA 1005 for `Clone` after `Next` and after `Skip`, on the enumerators of the request headers and the response headers alike, and after `Reset` on the request headers'. This is the response headers' enumerator after a `Reset`: after two `Next` calls and a `Reset`, the enumerator's `Next` returns the first header, `Ha`, and its clone's `Next` returns the third, `Hc`. Observed by running the reproducer's test cases. - -**To Reproduce** -Steps to reproduce the behavior: -1. Open `webview2-response-headers-reset-clone.twinproj` (attached as `webview2-response-headers-reset-clone.zip`). Its `Form1` holds one WebView2 control, which navigates to `https://example.com/page` and answers that request itself in `WebResourceRequested`, so nothing goes to the network. The handler appends four headers, `Ha` to `Hd`, to the response, and records what three fresh enumerators of `Request.Headers`, then three of `Response.Headers`, return (module `HeaderEnumeration`, below). -2. Call `HeaderEnumerationTests.Response_CloneAfterReset` from `Sub Main` and press F5. The form shows until its request is answered, and closes. -3. See the assertion fail with `Expected: "Ha|Ha" Actual: "Ha|Hc"`: the enumerator's `Next`, then the clone's. - -The module below is the reproducer's, and is meant to be adapted as a regression test. It shows `Form1` while the control answers its request, and unloads it. On BETA 1005 the five cases from #2496 pass, and `Response_CloneAfterReset` fails. -```vb -' Regression tests for #2496 and its follow-up. IEnumVARIANT::Clone returns an -' enumerator with the same state as the one it is called on (the COM -' documentation of IEnumVARIANT::Clone), so the clone's Next returns what the -' enumerator's own Next returns next. Each case compares the two after one Next, -' after Skip 2, and after two Next and a Reset, for the request headers and for -' the response headers, which hold Ha, Hb, Hc and Hd. - -' IEnumVARIANT, declared here because the package's own declaration is private. -[InterfaceId("00020404-0000-0000-C000-000000000046")] -Interface IEnumVARIANTOwn Extends stdole.IUnknown - Type CArrayOfVariants - Elements(1) As Variant - End Type - - Sub Next(ByVal celt As Long, ByRef rgVar As CArrayOfVariants, ByRef pCeltFetched As Long) - Sub Skip(ByVal celt As Long) - Sub Reset() - Function Clone() As stdole.IUnknown -End Interface - -' What the enumerators returned, recorded by Form1 while it answers its request. -Module HeaderEnumeration - - Public Recorded As Boolean - Public Results As New Collection ' " " -> "|" - - Private Function NextName(ByVal e As IEnumVARIANTOwn) As String - Dim arr As IEnumVARIANTOwn.CArrayOfVariants - Dim fetched As Long - e.Next 1, arr, fetched - If fetched = 0 Then Return "(end)" - Dim hd As WebView2Header = arr.Elements(0) - Return hd.Name - End Function - - ' Three fresh enumerators of one collection. - Public Sub Record(ByVal Side As String, ByVal u0 As stdole.IUnknown, ByVal u1 As stdole.IUnknown, ByVal u2 As stdole.IUnknown) - Dim e As IEnumVARIANTOwn, c As IEnumVARIANTOwn, first As String - - Set e = u0 - first = NextName(e) - Set c = e.Clone() - Results.Add NextName(e) & "|" & NextName(c), Side & " after one Next" - - Set e = u1 - NextName e - NextName e - e.Reset - Set c = e.Clone() - Results.Add NextName(e) & "|" & NextName(c), Side & " after Reset" - - Set e = u2 - e.Skip 2 - Set c = e.Clone() - Results.Add NextName(e) & "|" & NextName(c), Side & " after Skip 2" - End Sub - -End Module - -[TestFixture] -Module HeaderEnumerationTests - - Private Declare PtrSafe Function SetEnvironmentVariableW Lib "kernel32" (ByVal lpName As LongPtr, ByVal lpValue As LongPtr) As Long - - ' Shows Form1 until its request has been answered, once. - Private Sub Collect() - If HeaderEnumeration.Recorded Then Exit Sub - ' Only for an IDE that was started with these variables, as this repository's - ' test harness starts it: WebView2 would read them, and the control would fail. - Dim folder As String = Environ$("WEBVIEW2_USER_DATA_FOLDER") - Dim args As String = Environ$("WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS") - SetEnvironmentVariableW StrPtr("WEBVIEW2_USER_DATA_FOLDER"), 0 - SetEnvironmentVariableW StrPtr("WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS"), 0 - Form1.Show - Dim started As Double = Timer - Do Until HeaderEnumeration.Recorded Or Timer - started > 30 - DoEvents - Loop - Unload Form1 - If Len(folder) Then SetEnvironmentVariableW StrPtr("WEBVIEW2_USER_DATA_FOLDER"), StrPtr(folder) - If Len(args) Then SetEnvironmentVariableW StrPtr("WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS"), StrPtr(args) - End Sub - - ' "|" - Private Function Pair(ByVal Key As String) As String - Collect - Assert.Strict.IsTrue HeaderEnumeration.Recorded, "the request was answered" - Return HeaderEnumeration.Results(Key) - End Function - - ' Fixed in #2496 (BETA 1005): the clone continues from the enumerator's position. - - [TestCase] - Public Sub Request_CloneAfterNext() - Assert.Strict.AreEqual "Upgrade-Insecure-Requests|Upgrade-Insecure-Requests", Pair("request after one Next") - End Sub - - [TestCase] - Public Sub Request_CloneAfterSkip() - Assert.Strict.AreEqual "User-Agent|User-Agent", Pair("request after Skip 2") - End Sub - - [TestCase] - Public Sub Request_CloneAfterReset() - Assert.Strict.AreEqual "Accept|Accept", Pair("request after Reset") - End Sub - - [TestCase] - Public Sub Response_CloneAfterNext() - Assert.Strict.AreEqual "Hb|Hb", Pair("response after one Next") - End Sub - - [TestCase] - Public Sub Response_CloneAfterSkip() - Assert.Strict.AreEqual "Hc|Hc", Pair("response after Skip 2") - End Sub - - ' Left in BETA 1005: Reset does not rewind the position Clone copies. - - [TestCase] - Public Sub Response_CloneAfterReset() - Assert.Strict.AreEqual "Ha|Ha", Pair("response after Reset") - End Sub - -End Module -``` -`Form1`'s handlers, which call `Record`: -```vb - Private Sub WebView21_Ready() Handles WebView21.Ready - WebView21.AddWebResourceRequestedFilter "*", wv2All - WebView21.Navigate "https://example.com/page" - End Sub - - Private Sub WebView21_WebResourceRequested(ByVal Request As WebView2Request, ByVal Response As WebView2Response) _ - Handles WebView21.WebResourceRequested - Response.Headers.AppendHeader "Ha", "1" - Response.Headers.AppendHeader "Hb", "2" - Response.Headers.AppendHeader "Hc", "3" - Response.Headers.AppendHeader "Hd", "4" - If Not HeaderEnumeration.Recorded Then - Dim rq As WebView2RequestHeaders = Request.Headers - HeaderEnumeration.Record "request", rq._NewEnum(), rq._NewEnum(), rq._NewEnum() - Dim rs As WebView2ResponseHeaders = Response.Headers - HeaderEnumeration.Record "response", rs._NewEnum(), rs._NewEnum(), rs._NewEnum() - HeaderEnumeration.Recorded = True - End If - Response.StatusCode = 200 - Response.ReasonPhrase = "OK" - Response.ContentUTF8 = "probe" - End Sub -``` - -**Expected behavior** -`Ha|Ha`: `IEnumVARIANT::Clone` creates an enumerator with the same state as the current one, so after a `Reset` the clone starts at the first header, as the request headers' clone does. - -**Desktop:** - - OS: Windows 10 Pro 22H2 (build 19045) - - twinBASIC compiler version: BETA 1005 - -**Additional context** -In BETA 1005's package source, `WebView2HeadersCollection.twin`, `Next` and `Skip` advance `IterationIdx`, which `Clone` passes to the new enumerator. The two request headers classes set `IterationIdx = 0` in their `Reset`; the two response headers classes in `WebView2ResponseHeaders.twin`, `WebView2HeadersCollection_ResponseGetHeaders` and `WebView2HeadersCollection_ResponseEnumerator`, do not, so after a `Reset` their clone skips as many headers as were read before it. Setting `IterationIdx = 0` in those two `Reset`s, as the request side does, would fix it. Only `_NewEnum` (the `ResponseEnumerator` class) was run; `GetHeaders` (the other class) has the same `Reset`. - -Severity: low. `For Each` does not call `Clone`, so only a COM client that clones the enumerator after resetting it gets the wrong headers. - - - ---- - ## `import` and `export` exit with code 0 when they end `... FAILED` **Describe the bug** @@ -2500,55 +2049,6 @@ Severity: low. The console reports a failed build for a line that only asks to p --- -## A `ParamArray` element that is a `Variant` variable holding an array cannot be indexed or assigned to an array: error -2147467259 - -**Describe the bug** -When the argument passed to a `ParamArray` is a `Variant` variable that holds an array, the procedure cannot use the element as an array. Assigning it to an array variable of any type, indexing it to read or to write an element, and passing it on to another `ParamArray` that indexes it raise run-time error -2147467259, *Unspecified error*; `Join` raises error 13. VB6 does all of these without an error. `LBound`, `UBound`, `IsArray`, `VarType` and `For Each` work on the same element. Observed in the reproducer project's regression tests and in a run of its `Sub Main`. - -**To Reproduce** -Steps to reproduce the behavior: -1. Open `paramarray-variant-array.twinproj` (attached as `paramarray-variant-array.zip`). Its module `Cases` holds 28 cases: each passes a small array to a `ParamArray` function in one way, does one thing with `parts(0)`, and returns what that gives, or `error `. Its module `ParamArrayTests` holds one `[TestCase]` per case, asserting what the case gives in VB6. -2. Run it (F5). `Sub Main` prints what each case gives; the same program built in VB6 (attached as `paramarray-variant-array-vb6.zip`) prints what VB6 gives: - - | the argument | what the function does with `parts(0)` | twinBASIC | VB6 | - |---|---|---|---| - | `b`, a `Byte()` variable | `a = parts(0)`, `a() As Byte` | `2 98` | `2 98` | - | `v`, a `Variant` variable holding a `Byte()` | `a = parts(0)`, `a() As Byte` | error -2147467259 | `2 98` | - | a `Variant` variable holding a `Long()`, `String()`, `Double()` or `Variant()` | `a = parts(0)`, `a()` of that type | error -2147467259 | `2 7`, `2 y`, `2 2.5`, `2 y` | - | `v` | `x = parts(0)(1)` | error -2147467259 | `98` | - | a `Variant` variable holding a two-dimensional `Long()` | `x = parts(0)(1, 2)` | error -2147467259 | `12` | - | a `Variant` variable holding `Array(1, 2, 3)` | `parts(0)(1) = 99` | error -2147467259, and the caller's element 1 stays 2 | no error, and the caller's element 1 is 99 | - | a `Variant` variable holding a `String()` | `Join(parts(0), "+")` | error 13 | `x+y+z` | - | `v` | passed on to another `ParamArray` function, which indexes its element | error -2147467259 | `98` | - | a `Variant` variable holding `Array(1, -2, 300, 20.3)` | the `Max` and `UnpackParamArray` of #1660 | error -2147467259 | `300` | - | a module-level `Variant`, an element of a `Variant()` array, a `Variant` field of a UDT, or a `ByVal` or `ByRef` `Variant` parameter passed on | `x = parts(0)(1)` | error -2147467259 | `98` | - | `v`, to a class's method that takes the `ParamArray`, called early- or late-bound | `x = parts(0)(1)` | error -2147467259 | `98` | - | `v` | `LBound` and `UBound`; `IsArray` and `VarType`; `For Each` | `0 to 2`; `True 8209`; the sum | the same | - | `v` | passed on to a `ByVal x As Variant` parameter, which indexes it | `98` | `98` | - | `v` | the whole `ParamArray` passed on to another, which reads `q(0)(0)(1)` | `98` | `98` | - | `v` | `x = parts(0)`, then `x(1)` | `98` | `98` | - | `(v)`, `CVar(b)`, or a function's `Variant` result | `x = parts(0)(1)` | `98` | `98` | - -**Expected behavior** -What VB6 gives, in every case. The element refers to the caller's `Variant`, which holds an array: assigning the element to an array variable copies that array, indexing it reads or writes the array's elements, and `Join` joins them. - -**Desktop:** - - OS: Windows 10 Pro 22H2 (build 19045) - - twinBASIC compiler version: BETA 1005 - -**Additional context** -The same on BETA 997 and 957, and in a built exe. In an exe compiled with LLVM every case gives the same, but for the case of #1660, which ends the program with an access violation: its error is raised in `UnpackParamArray`, which has no error handler, and is not passed on to the handler of its caller, a separate defect of LLVM-compiled code. - -What does not reproduce it: an argument that is an array variable, or an expression such as `(v)`, `CVar(b)` or a function's result, which passes a copy rather than a reference to a `Variant`; the element read into a local `Variant` first, or passed to a `ByVal Variant` parameter; the whole `ParamArray` passed on to another; and outside a `ParamArray`, a `ByRef Variant` parameter (`a = v` works) or an element of a `Variant()` array assigned in the procedure the array is passed to. - -#1660 (open) is this defect reached another way: its unpacking routine is one of the cases, and fails as reported there. This entry narrows it to the element of a `ParamArray` that refers to a `Variant` variable, and shows what else fails with it. - -Severity: moderate. An error is always raised, so nothing goes wrong silently, but VB6 code that forwards a `Variant` array through a `ParamArray`, such as a `Max` or `Concat` helper or the unpacking routine of #1660, fails, and *Unspecified error* gives no hint why. Reading the element into a `Variant` first avoids it. - - - ---- - ## The IDE's `styles.css` labels a GIF and a JPEG `image/png`, and two PNGs `/png` **Describe the bug** @@ -2633,285 +2133,146 @@ Severity: high for a program compiled with LLVM. An error that the caller handle --- -## The shipped `Dark.theme` and `Light.theme` lack a `;` after `DiagnosticsGroupFontSize`, so the IDE loads neither it nor `DiagnosticsGroupFontWeight` +## Format Selection from a continuation line fails with *IDE startup failure*, and closes an IDE that was opened with its project **Describe the bug** -Line 63 of both `themes\Dark.theme` and `themes\Light.theme`, the two theme files that the IDE ships and that every other theme inherits from, reads `DiagnosticsGroupFontSize: 90%` with no `;` at its end. The IDE's theme loader cuts a theme file into properties at each `;`, so the property runs into the next line, and the piece it cuts is not a property. The loader drops such a piece without a message, so both `DiagnosticsGroupFontSize` and `DiagnosticsGroupFontWeight` are lost. Each of the two files has 495 properties that load, and these 2 are not among them. No other piece of the three shipped theme files (`Classic.theme` is the third) is dropped. +Edit > Format Selection on a selection whose first line continues the line above it (the line above ends in ` _`) formats nothing, and the IDE's page raises `TypeError: Assignment to constant variable.` When the IDE was started with the project on its command line, as it is when a `.twinproj` is opened from Explorer, the error is shown as an alert headed *IDE startup failure*, and after the alert the IDE closes itself: at once when nothing is unsaved, and after asking whether to save when something is. Started from its icon, with the project opened from the New / Open dialog, the IDE prints the same error in the DEBUG CONSOLE and stays open. It was observed in an IDE started with an `APPDATA` of its own, by recording the page's alerts and watching the IDE's process. **To Reproduce** Steps to reproduce the behavior: -1. Open `themes\Dark.theme`, in the folder of the twinBASIC installation, in a text editor. There is no project to attach: the defect is in the installation's own files. -2. Lines 63 and 64 read: +1. Open a project by double-clicking its `.twinproj`, so that the IDE starts with it. Add this procedure to a module, and save: ``` - DiagnosticsGroupFontSize: 90% - DiagnosticsGroupFontWeight: 500; + Public Sub Continued() + Dim a As Long, _ + b As Long + Select Case a + Case 1 + a = 2 + End Select + End Sub ``` - Line 63 ends without the `;` that ends line 64 and every other property of the file. (In the file, the spaces after each colon are tabs.) `themes\Light.theme` has the same two lines at the same place. -3. Open `ide\main.js` and find `applyThemeData`, the function that loads a theme file. It removes the `/* */` comments, cuts the text at each `;` followed by a space, a tab or a line end, removes the line breaks from each piece, cuts each piece at each `: ` and `:` followed by a tab, and keeps a piece only if that gives exactly two parts (the name and the value). Lines 63 and 64 are one piece, `DiagnosticsGroupFontSize:` and its value, then `DiagnosticsGroupFontWeight:` and its value, with nothing between them but the line break the loader removes. It gives three parts, so the piece is dropped. The loader's code has no other branch for a piece, and it writes no message. -4. Instead of reading the code, run `node scripts/probe_theme_semicolon.mjs` from the repository. It applies the same rule to every `.theme` file in the install's `themes` folder, and prints one line for each piece it drops. It starts no IDE. +2. Select from the start of the line `b As Long` to the end of `Select Case a`, and choose Edit > Format Selection. +3. An alert reads `IDE startup failure (BETA twinBASIC IDE BETA 1005):`, then `JS Promise Rejection(1):`, `Assignment to constant variable.` and a stack trace through `provideDocumentRangeFormattingEdits`. Nothing is formatted. Click OK: the IDE closes. +4. Open the project again, change anything, and repeat step 2. After the alert the IDE asks *You've made changes to this project. Save now?*, with Save, Discard Changes and Cancel. Cancel keeps the IDE open; the other two close it. **Expected behavior** -Both properties load, with the values the files give: a font size of `90%` and a font weight of `500` for the Diagnostics group. That needs a `;` at the end of line 63 in both files. Any other property run together with the next one is lost in the same way, so the loader could also say which piece it dropped, in the DEBUG CONSOLE or the IDE's trace output. +The selection is re-indented, as it is when it starts on any other line: the formatter moves the start up to the first line of the continued statement and formats from there. A failure inside a command does not close the IDE. **Desktop:** - OS: Windows 10 Pro 22H2 (build 19045) - twinBASIC compiler version: BETA 1005 **Additional context** -BETA 997 and 957 have the same two lines at the same place. `Classic.theme` has no such piece: it contains `inherits: light;` and its own properties, all of which end with `;`. The loader's rule was checked by applying the code of `applyThemeData` unchanged to each file: `Dark.theme` and `Light.theme` give 495 properties each, without the two, and `Classic.theme` gives 188. +The cause, read from `ide\main.js` and `ide\main2.js`: `provideDocumentRangeFormattingEdits` declares the start line with `const` and then decrements it (`o--`) in the loop that moves the start up over the lines ending in `_`, which throws as soon as the selection starts on a continuation line. The rejection reaches `window.onunhandledrejection` in `main2.js`, which alerts *IDE startup failure* and calls `hostAppObject.CloseWindow` unless `reportIDEerrorsToDebugConsole` is set, and only `startNow`, the start without a project, sets it. So in an IDE opened with its project, any unhandled rejection in the page closes the IDE. -The effect on screen was not observed. In the IDE's `styles.css`, the rule of `.problemNodeGroup` reads `font-weight: var(--themeDiagnosticsGroupFontWeight); font-size: var(--themeDiagnosticsGroupFontSize);`, and the loader sets one CSS variable for each property it loads, so with these two missing, the variables have no value. By the CSS rules, a `var()` of a variable with no value makes the declaration invalid when the style is computed, and `font-size` and `font-weight` then take the value they inherit. So the elements of that class (by its name, the group headings of the Problems panel) would use the size and weight they inherit instead of 90% and 500. +Format Selection on a selection that starts on any other line, the `Select Case` line included, works. Format Document, on the same text, works. -Severity: low. No data is lost, and the look of one kind of heading is the only thing that changes, but the IDE says nothing about it. A theme file that sets the two properties again, with its `;`, avoids it: a custom theme that inherits from Dark or Light, with `DiagnosticsGroupFontSize: 90%;` and `DiagnosticsGroupFontWeight: 500;` among its own lines, loads them. +Severity: high. The IDE closes during ordinary editing. Nothing is lost if the question in step 4 is answered Save or Cancel, but Discard Changes, a likely answer to a question nobody expected, loses every unsaved change. - + --- -## A `Dark.theme` or `Light.theme` in `%APPDATA%\twinBASIC\themes` stops every custom theme from inheriting, and Reload from disk from applying the theme +## Find with Whole Word Only never finds a match at the very start of the file **Describe the bug** -When the common themes folder, `%APPDATA%\twinBASIC\themes`, holds a file named `Dark.theme` or `Light.theme`, no custom theme is linked to the theme it inherits from. Window > Theme lists every custom theme, the shipped Classic included, as ` (undefined)` instead of ` (Light)` or ` (Dark)`. Choosing one gives the dark base whatever its `inherits:` line says, with only the theme's own properties changed and every other colour left as the previous theme had it. Window > Theme > Reload from disk then reads the files again but never applies the active theme, so an edit to it does not show until the IDE is restarted. No message is shown. It was observed in an IDE started with an `APPDATA` of its own, by reading the theme list and the CSS variables of the IDE's page. +With Whole Word Only ticked, the Find / Replace panel never finds a match that begins at line 1, column 1 of the file. Find Next passes over it, and the search ends with *The specified region has been searched.* No message says that a match was skipped. It was observed by operating the panel with mouse and key events in an IDE started with an `APPDATA` of its own, and reading the selection after each Find Next. **To Reproduce** Steps to reproduce the behavior: -1. Close the IDE. There is no project to open: the defect is in how the IDE reads its theme folders. In `%APPDATA%\twinBASIC\themes` (the IDE creates the folder), write `AtStart.theme`: +1. Open any project and a module whose first line is its `Module` line, with nothing above it, such as: ``` - inherits: light; - MenuBackColor: rgb(255, 0, 0); + Module Finder + Public Sub Main() + Debug.Print 1 + End Sub + End Module ``` - and `Dark.theme`, with any text: its contents are not read. -2. Start the IDE and cancel the New / Open dialog. Open Window > Theme: it lists `Classic (undefined)` and `AtStart (undefined)`. -3. Choose `AtStart`: the IDE takes the dark base (dark panels, the dark code editor) with a red menu bar. -4. Change the colour in `AtStart.theme` to `rgb(0, 128, 0)` and choose Window > Theme > Reload from disk: the menu bar stays red. -5. Delete `Dark.theme` and restart the IDE: the list shows `Classic (Light)` and `AtStart (Light)`, `AtStart` gives the light base with a red menu bar, and Reload from disk after the same edit turns the menu bar green at once. +2. Put the caret on line 2 and press Ctrl+F. Type `Module` in Find What and tick Whole Word Only; Current File and Direction All are as Ctrl+F sets them. +3. Click Find Next: the `Module` of `End Module` is selected. Click Find Next again: *The specified region has been searched.* So it goes on every later press: the `Module` at line 1, column 1 is never selected. +4. Clear Whole Word Only and repeat from step 2: the second press selects line 1, column 1. **Expected behavior** -The themes keep their parents whatever else the common folder holds, as in step 5: `Classic (Light)` and `AtStart (Light)` in the list, the light base for `AtStart`, and Reload from disk applying the edit. A `Dark.theme` or `Light.theme` there is either ignored, as the code means it to be, or replaces the shipped theme of that name. +The second press selects the `Module` at line 1, column 1. The start of the file is a word boundary, as the start of any other line is. **Desktop:** - OS: Windows 10 Pro 22H2 (build 19045) - twinBASIC compiler version: BETA 1005 **Additional context** -The cause, read from `ide\main.js`: `reloadThemesFromDisk`, which runs at start-up and for Reload from disk, counts every `.theme` file of both folders into `customThemesCount`, but in the common folder it skips `Dark.theme` and `Light.theme` without calling `addTheme`. `addTheme` links the themes to their parents (`inheritsFromLink` and `group`), and runs `afterRefreshedThemes`, only once `customThemes.length` reaches `customThemesCount`, which then never happens. Reload from disk applies the theme in `afterRefreshedThemes`, so that never runs either. Counting only the files that are added would cure both. - -Only `Dark.theme` was tried; `Light.theme` takes the same branch of the code. The file was in place when the IDE started; adding it and choosing Reload from disk goes through the same function, and was not tried. +The cause, read from `ide\main.js`: `doFindMatch` takes the character before and the character after a match with `charAt`, and treats each as a boundary when its `charCodeAt(0)` is below 128 and it is not a letter or a digit. Before the first character of the text searched, `charAt` returns an empty string, whose `charCodeAt(0)` is `NaN`, so the match is taken to be inside a word and is skipped. The same code skips a match that ends at the last character of the text searched; that, and a match that starts exactly at the caret (the text searched starts there), were not tried. -Severity: moderate. Nothing is lost, but copying the shipped `Dark.theme` into the common folder to change it, the obvious way to adjust a shipped theme, makes every custom theme wrong, and nothing says why. +Severity: moderate. The search reports the region searched while it has skipped a match, and nothing shows it. - + --- -## Two themes that inherit from each other freeze the IDE, and choosing one fails with a stack overflow +## Find with Regular Expressions finds nothing when the pattern holds a capital letter **Describe the bug** -When two theme files name each other in their `inherits:` lines, loading the themes stops the IDE's page for several seconds and leaves the themes half linked: one of the two is listed as ` (undefined)`. Choosing either of them afterwards changes nothing on screen: the page raises an uncaught `RangeError: Maximum call stack size exceeded` while it applies the theme, and the previous theme's colours stay. Nothing on screen reports the error. It was observed in an IDE started with an `APPDATA` of its own, with a timer in the IDE's page recording the longest time the page did not run, and a listener for the page's `error` event catching the exception. +With Regular Expressions ticked, a pattern that holds a capital letter finds nothing unless it carries the `i` flag: `/End Sub/` ends with *The specified region has been searched.* in a file full of `End Sub`, while `/end sub/` and `/End Sub/i` find it. With Match Case clear, the panel searches the file's text in lower case but leaves the regular expression as it was typed; and ticking Regular Expressions clears Match Case (ticking Match Case clears Regular Expressions), so a regular expression always runs against the text in lower case. It was observed by operating the panel with mouse and key events in an IDE started with an `APPDATA` of its own. **To Reproduce** Steps to reproduce the behavior: -1. Start the IDE. There is no project to open. In `%APPDATA%\twinBASIC\themes`, write `LoopA.theme`: - ``` - inherits: LoopB; - MenuBackColor: rgb(1, 1, 1); - ``` - and `LoopB.theme`: - ``` - inherits: LoopA; - MenuBackColor: rgb(2, 2, 2); - ``` -2. Choose Window > Theme > Reload from disk. The IDE does not respond for about seven seconds (7.5 s on the machine it was found on). -3. Open Window > Theme: it lists `LoopB (undefined)`. -4. Choose `LoopA`: the menu bar does not take `rgb(1, 1, 1)`, and nothing else changes. -5. To recover, remove the loop from the files and restart the IDE. +1. Open any project and a module that holds `End Sub`. Press Ctrl+F and tick Regular Expressions; Match Case is cleared. +2. Type `/End Sub/` in Find What, put the caret at line 1, column 1, and click Find Next: *The specified region has been searched.* +3. Type `/end sub/`, or `/End Sub/i`, instead: Find Next selects `End Sub`. **Expected behavior** -The IDE stays responsive, and the themes in the loop fall back to the Dark theme, which the code in `addTheme` means them to do: it checks for a theme met twice, and sets the group to Dark when it finds one. Choosing `LoopA` then applies at least its own `MenuBackColor`. A message naming the loop would help the person who wrote it. +`/End Sub/` finds `End Sub`. Either the search runs on the text as it is, with the `i` flag added when Match Case is clear, or Match Case can be ticked together with Regular Expressions to make the search case-sensitive. **Desktop:** - OS: Windows 10 Pro 22H2 (build 19045) - twinBASIC compiler version: BETA 1005 **Additional context** -The cause, read from `ide\main.js`: in `addTheme`, the pass that gives each theme its group follows `inheritsFromLink` in a `while` loop and records each theme it passes. When it meets a theme a second time it sets the group to Dark, but does not leave the loop, so it keeps going round the two themes until it stops after several seconds without finishing: `LoopB` is never given a group, and `afterRefreshedThemes` is still pending afterwards. `applyThemeData` then follows the same links recursively when a theme of the loop is applied, and overflows the stack. A `break` after the fall-back, and the same check in `applyThemeData`, would cure both. - -Start-up loads the themes through the same `reloadThemesFromDisk`, so an IDE started with the two files in place should freeze the same way; only Reload from disk was tried. A longer chain that returns to its first theme takes the same path in the code, and was not tried. +The cause, read from `ide\main.js`: the Find Next code lower-cases the text searched (`toLowerCase`) when Match Case is clear, and `createRegexFromUserString` builds the expression from the pattern as typed, adding only the `m` flag. A class such as `\D` (not a digit) matches lower-case text as before, so `/\Dim/` still finds `Dim`: only a literal capital letter fails. -Severity: moderate. Nothing is lost and the IDE recovers, but the freeze looks like a hang, and nothing says which files cause it. +Severity: moderate. The search reports nothing found for text that is there. - + --- -## A keyboard-shortcut group whose name contains `|` switches other groups on and off +## A regular-expression match that spans a line break is shown as an empty selection, and Find Next does not move past it **Describe the bug** -The IDE keeps the list of keyboard-shortcut groups that are on as one string, each name between two bars, such as `|Mine||Other|`, and looks a name up in it as text. Manage Keyboard Shortcuts accepts a group name that contains `|`, and that name's entry can then contain another group's: with groups `A|B` and `B`, the text `|A|B|` is the entry of `A|B` and also contains `|B|`. Clicking `B` in Window > Keyboard Shortcuts then removes the wrong text: the first click leaves `B` ticked, and the second switches `A|B` off as well and saves a list that ends in a broken `|A`. Nothing warns about the name. It was observed by operating Manage Keyboard Shortcuts and the commands of the Window > Keyboard Shortcuts items in an IDE whose saves were blocked, and reading the list and the menu's ticks after each step. +When a regular expression in the Find / Replace panel matches across a line break, Find Next selects nothing: it puts the caret at the end of the line where the match starts, with an empty selection. The next Find Next finds the same match and stays at the same place, so the search can go no further. It was observed by operating the panel with mouse and key events in an IDE started with an `APPDATA` of its own, and reading the selection after each Find Next. **To Reproduce** Steps to reproduce the behavior: -1. There is no project to open. The steps change the groups saved in `HKCU\Software\VB and VBA Program Settings\twinBASIC_IDE\IDESettings` (`CUSTOM_KEYBOARD_SHORTCUT_GROUPS` and `ACTIVE_KEYBOARD_SHORTCUT_GROUPS`): export that key first if they matter. Choose Window > Keyboard Shortcuts > Manage Keyboard Shortcuts..., Add New..., type `A|B`, press Enter, and Save Changes. The new group is switched on. -2. Do the same with the name `B`. Window > Keyboard Shortcuts shows `Custom 1. A|B` and `Custom 2. B`, both ticked. The list of groups that are on is `|A|B||B|`. -3. Click `Custom 2. B`. It stays ticked: the IDE removed the first `|B|` it found, which is the one inside `|A|B|`, and the list is now `|A|B|`. -4. Click `Custom 2. B` again. `A|B` and `B` are both unticked, though only `B` was clicked, and the list saved is `|A`, which names no group. +1. Open any project and a module in which an indented line comes before `End Sub`, such as: + ``` + Module Finder + Public Sub Main() + Debug.Print 1 + End Sub + End Module + ``` +2. Press Ctrl+F, tick Regular Expressions and type `/^\s+end sub$/` in Find What (in lower case: see the entry about capital letters). Put the caret at line 1, column 1 and click Find Next. +3. The selection is empty, at the end of the `Debug.Print` line. Click Find Next again: the selection is at the same place, however often it is clicked. **Expected behavior** -Step 3 switches `B` off and leaves `A|B` on. Either Manage Keyboard Shortcuts refuses a name that contains `|`, as it refuses one that starts with `<`, or the list is kept in a form that a name cannot break, such as a JSON array of names. +The match is selected, from the line break to the end of `End Sub`, and the next Find Next goes on from the end of the match. **Desktop:** - OS: Windows 10 Pro 22H2 (build 19045) - twinBASIC compiler version: BETA 1005 **Additional context** -The code, read from `ide\main.js`: `toggleActiveKeyboardShortcutName` tests the list with `indexOf("|" + name + "|")` and removes the entry with `replace`, which replaces the first match; `getCustomKeyboardShortcutMenuOptions` ticks a group by the same `indexOf`. The merge of the groups that are on matches names the same way: with the list `|A|B|`, `A|B` on and `B` defined but never switched on, a shortcut that only `B` defines works, and `B` is ticked in the menu. That was tried with the groups set in the IDE's page as Save Changes sets them, and a real key press. +The cause, read from `ide\main.js`: `doFindMatch` turns the start of the match into a line and a column, but puts its end on the same line, at the start column plus the match's length. For a match that holds a line break that column lies past the end of the line, and the editor shortens the selection to the line's end. The next search starts from that place, which is before the match, and finds it again. The match starts at the line break because the IDE adds the `m` flag, so `^` also matches inside the line break. A match within one line, such as `/sub$/i`, is selected properly. -Severity: low. A name with `|` is unusual, and the groups themselves are kept, but the saved list is corrupted without a message, and the menu shows a group as on after it was switched off. +Severity: low. Patterns that span lines are rare, but Find Next cannot get past one. - + --- -## Format Selection from a continuation line fails with *IDE startup failure*, and closes an IDE that was opened with its project +## The Find / Replace panel's *Invalid RegExp:* alert leaves out the reason **Describe the bug** -Edit > Format Selection on a selection whose first line continues the line above it (the line above ends in ` _`) formats nothing, and the IDE's page raises `TypeError: Assignment to constant variable.` When the IDE was started with the project on its command line, as it is when a `.twinproj` is opened from Explorer, the error is shown as an alert headed *IDE startup failure*, and after the alert the IDE closes itself: at once when nothing is unsaved, and after asking whether to save when something is. Started from its icon, with the project opened from the New / Open dialog, the IDE prints the same error in the DEBUG CONSOLE and stays open. It was observed in an IDE started with an `APPDATA` of its own, by recording the page's alerts and watching the IDE's process. - -**To Reproduce** -Steps to reproduce the behavior: -1. Open a project by double-clicking its `.twinproj`, so that the IDE starts with it. Add this procedure to a module, and save: - ``` - Public Sub Continued() - Dim a As Long, _ - b As Long - Select Case a - Case 1 - a = 2 - End Select - End Sub - ``` -2. Select from the start of the line `b As Long` to the end of `Select Case a`, and choose Edit > Format Selection. -3. An alert reads `IDE startup failure (BETA twinBASIC IDE BETA 1005):`, then `JS Promise Rejection(1):`, `Assignment to constant variable.` and a stack trace through `provideDocumentRangeFormattingEdits`. Nothing is formatted. Click OK: the IDE closes. -4. Open the project again, change anything, and repeat step 2. After the alert the IDE asks *You've made changes to this project. Save now?*, with Save, Discard Changes and Cancel. Cancel keeps the IDE open; the other two close it. - -**Expected behavior** -The selection is re-indented, as it is when it starts on any other line: the formatter moves the start up to the first line of the continued statement and formats from there. A failure inside a command does not close the IDE. - -**Desktop:** - - OS: Windows 10 Pro 22H2 (build 19045) - - twinBASIC compiler version: BETA 1005 - -**Additional context** -The cause, read from `ide\main.js` and `ide\main2.js`: `provideDocumentRangeFormattingEdits` declares the start line with `const` and then decrements it (`o--`) in the loop that moves the start up over the lines ending in `_`, which throws as soon as the selection starts on a continuation line. The rejection reaches `window.onunhandledrejection` in `main2.js`, which alerts *IDE startup failure* and calls `hostAppObject.CloseWindow` unless `reportIDEerrorsToDebugConsole` is set, and only `startNow`, the start without a project, sets it. So in an IDE opened with its project, any unhandled rejection in the page closes the IDE. - -Format Selection on a selection that starts on any other line, the `Select Case` line included, works. Format Document, on the same text, works. - -Severity: high. The IDE closes during ordinary editing. Nothing is lost if the question in step 4 is answered Save or Cancel, but Discard Changes, a likely answer to a question nobody expected, loses every unsaved change. - - - ---- - -## Find with Whole Word Only never finds a match at the very start of the file - -**Describe the bug** -With Whole Word Only ticked, the Find / Replace panel never finds a match that begins at line 1, column 1 of the file. Find Next passes over it, and the search ends with *The specified region has been searched.* No message says that a match was skipped. It was observed by operating the panel with mouse and key events in an IDE started with an `APPDATA` of its own, and reading the selection after each Find Next. - -**To Reproduce** -Steps to reproduce the behavior: -1. Open any project and a module whose first line is its `Module` line, with nothing above it, such as: - ``` - Module Finder - Public Sub Main() - Debug.Print 1 - End Sub - End Module - ``` -2. Put the caret on line 2 and press Ctrl+F. Type `Module` in Find What and tick Whole Word Only; Current File and Direction All are as Ctrl+F sets them. -3. Click Find Next: the `Module` of `End Module` is selected. Click Find Next again: *The specified region has been searched.* So it goes on every later press: the `Module` at line 1, column 1 is never selected. -4. Clear Whole Word Only and repeat from step 2: the second press selects line 1, column 1. - -**Expected behavior** -The second press selects the `Module` at line 1, column 1. The start of the file is a word boundary, as the start of any other line is. - -**Desktop:** - - OS: Windows 10 Pro 22H2 (build 19045) - - twinBASIC compiler version: BETA 1005 - -**Additional context** -The cause, read from `ide\main.js`: `doFindMatch` takes the character before and the character after a match with `charAt`, and treats each as a boundary when its `charCodeAt(0)` is below 128 and it is not a letter or a digit. Before the first character of the text searched, `charAt` returns an empty string, whose `charCodeAt(0)` is `NaN`, so the match is taken to be inside a word and is skipped. The same code skips a match that ends at the last character of the text searched; that, and a match that starts exactly at the caret (the text searched starts there), were not tried. - -Severity: moderate. The search reports the region searched while it has skipped a match, and nothing shows it. - - - ---- - -## Find with Regular Expressions finds nothing when the pattern holds a capital letter - -**Describe the bug** -With Regular Expressions ticked, a pattern that holds a capital letter finds nothing unless it carries the `i` flag: `/End Sub/` ends with *The specified region has been searched.* in a file full of `End Sub`, while `/end sub/` and `/End Sub/i` find it. With Match Case clear, the panel searches the file's text in lower case but leaves the regular expression as it was typed; and ticking Regular Expressions clears Match Case (ticking Match Case clears Regular Expressions), so a regular expression always runs against the text in lower case. It was observed by operating the panel with mouse and key events in an IDE started with an `APPDATA` of its own. - -**To Reproduce** -Steps to reproduce the behavior: -1. Open any project and a module that holds `End Sub`. Press Ctrl+F and tick Regular Expressions; Match Case is cleared. -2. Type `/End Sub/` in Find What, put the caret at line 1, column 1, and click Find Next: *The specified region has been searched.* -3. Type `/end sub/`, or `/End Sub/i`, instead: Find Next selects `End Sub`. - -**Expected behavior** -`/End Sub/` finds `End Sub`. Either the search runs on the text as it is, with the `i` flag added when Match Case is clear, or Match Case can be ticked together with Regular Expressions to make the search case-sensitive. - -**Desktop:** - - OS: Windows 10 Pro 22H2 (build 19045) - - twinBASIC compiler version: BETA 1005 - -**Additional context** -The cause, read from `ide\main.js`: the Find Next code lower-cases the text searched (`toLowerCase`) when Match Case is clear, and `createRegexFromUserString` builds the expression from the pattern as typed, adding only the `m` flag. A class such as `\D` (not a digit) matches lower-case text as before, so `/\Dim/` still finds `Dim`: only a literal capital letter fails. - -Severity: moderate. The search reports nothing found for text that is there. - - - ---- - -## A regular-expression match that spans a line break is shown as an empty selection, and Find Next does not move past it - -**Describe the bug** -When a regular expression in the Find / Replace panel matches across a line break, Find Next selects nothing: it puts the caret at the end of the line where the match starts, with an empty selection. The next Find Next finds the same match and stays at the same place, so the search can go no further. It was observed by operating the panel with mouse and key events in an IDE started with an `APPDATA` of its own, and reading the selection after each Find Next. - -**To Reproduce** -Steps to reproduce the behavior: -1. Open any project and a module in which an indented line comes before `End Sub`, such as: - ``` - Module Finder - Public Sub Main() - Debug.Print 1 - End Sub - End Module - ``` -2. Press Ctrl+F, tick Regular Expressions and type `/^\s+end sub$/` in Find What (in lower case: see the entry about capital letters). Put the caret at line 1, column 1 and click Find Next. -3. The selection is empty, at the end of the `Debug.Print` line. Click Find Next again: the selection is at the same place, however often it is clicked. - -**Expected behavior** -The match is selected, from the line break to the end of `End Sub`, and the next Find Next goes on from the end of the match. - -**Desktop:** - - OS: Windows 10 Pro 22H2 (build 19045) - - twinBASIC compiler version: BETA 1005 - -**Additional context** -The cause, read from `ide\main.js`: `doFindMatch` turns the start of the match into a line and a column, but puts its end on the same line, at the start column plus the match's length. For a match that holds a line break that column lies past the end of the line, and the editor shortens the selection to the line's end. The next search starts from that place, which is before the match, and finds it again. The match starts at the line break because the IDE adds the `m` flag, so `^` also matches inside the line break. A match within one line, such as `/sub$/i`, is selected properly. - -Severity: low. Patterns that span lines are rare, but Find Next cannot get past one. - - - ---- - -## The Find / Replace panel's *Invalid RegExp:* alert leaves out the reason - -**Describe the bug** -When the pattern in Find What is not a valid regular expression, Find Next shows an alert that reads only *Invalid RegExp:*, without saying what is wrong with the pattern. It was observed by recording the alerts of the IDE's page while operating the panel. +When the pattern in Find What is not a valid regular expression, Find Next shows an alert that reads only *Invalid RegExp:*, without saying what is wrong with the pattern. It was observed by recording the alerts of the IDE's page while operating the panel. **To Reproduce** Steps to reproduce the behavior: @@ -3684,238 +3045,6 @@ Severity: low. A compile error is reported, nothing goes wrong silently, and a d --- -## `DateAdd` with a month, quarter or year interval runs into the next month instead of stopping at the last day of a shorter month - -**Describe the bug** -`DateAdd` with the interval `"m"`, `"q"` or `"yyyy"` keeps the day of the month and lets it overflow into the next month, where it should stop at the last day of the month it lands in. `DateAdd("m", 1, #1/31/2026#)` returns March 3, 2026, the date `DateSerial(2026, 2, 31)` gives, and not February 28. No error is raised, so nothing shows that the date is wrong. The VBA documentation, and twinBASIC's own page for `DateAdd` after it, say that `DateAdd` does not return an invalid date and that adding a month to January 31 returns February 28; VB6 does that. The other intervals (`"d"`, `"y"`, `"w"`, `"ww"`, `"h"`, `"n"` and `"s"`) give the right dates. Observed in the IDE, with the result of each call written to the DEBUG CONSOLE as `Format(..., "yyyy-mm-dd")`. - -**To Reproduce** -Steps to reproduce the behavior: -1. Open `dateadd-month-end-overflow.twinproj` (attached as `dateadd-month-end-overflow.zip`). Its one source file, `Startup.twin`, holds 18 calls, the first four of which are enough: - ``` - Module Startup - - Public Sub Main() - Debug.Print Format(DateAdd("m", 1, #1/31/2026#), "yyyy-mm-dd") - Debug.Print Format(DateAdd("m", -1, #3/31/2026#), "yyyy-mm-dd") - Debug.Print Format(DateAdd("yyyy", 1, #2/29/2024#), "yyyy-mm-dd") - Debug.Print Format(DateAdd("q", 1, #11/30/2025#), "yyyy-mm-dd") - End Sub - - End Module - ``` -2. Run the project in the IDE (F5) and read the DEBUG CONSOLE. -3. See `2026-03-03`, `2026-03-03`, `2025-03-01` and `2026-03-02`. VB6 (the same calls, attached as `dateadd-month-end-overflow-vb6.zip`, which writes each result to `out.txt`) gives `2026-02-28`, `2026-02-28`, `2025-02-28` and `2026-02-28`. The calls of the project, with the dates each gives: - - | call | twinBASIC | VB6 | - |---|---|---| - | `DateAdd("m", 1, #1/31/2026#)` | 2026-03-03 | 2026-02-28 | - | the same, with the date in a `Date` variable | 2026-03-03 | 2026-02-28 | - | `DateAdd("m", 1, "31-Jan-95")` | 1995-03-03 | 1995-02-28 | - | `DateAdd("m", 1, #1/30/2026#)` | 2026-03-02 | 2026-02-28 | - | `DateAdd("m", 1, #1/29/2026#)` | 2026-03-01 | 2026-02-28 | - | `DateAdd("m", 1, #1/31/2024#)` | 2024-03-02 | 2024-02-29 | - | `DateAdd("m", 1, #10/31/2026#)` | 2026-12-01 | 2026-11-30 | - | `DateAdd("m", -1, #3/31/2026#)` | 2026-03-03 | 2026-02-28 | - | `DateAdd("m", -1, #3/30/2026#)` | 2026-03-02 | 2026-02-28 | - | `DateAdd("yyyy", 1, #2/29/2024#)` | 2025-03-01 | 2025-02-28 | - | `DateAdd("yyyy", -1, #2/29/2024#)` | 2023-03-01 | 2023-02-28 | - | `DateAdd("q", 1, #11/30/2025#)` | 2026-03-02 | 2026-02-28 | - -What decides it: the result is the date `DateSerial(year, month + number, day)` gives, so a day that exists in the target month is not affected, and one that does not is counted into the month after. -- A day that exists in the target month is right: `DateAdd("m", 1, #1/28/2026#)` is 2026-02-28, and `DateAdd("yyyy", 4, #2/29/2024#)` is 2028-02-29, in both. -- The other intervals are right: on January 31, 2026, `"d"` and `"y"` with 1 give 2026-02-01 and `"ww"` with 1 gives 2026-02-07, in both. -- It makes no difference whether the date is a literal, a `Date` variable, `DateSerial(2026, 1, 31)` or a string; whether the number is positive or negative; or whether the date has a time of day, which is kept (`DateAdd("m", 1, #1/31/2026 13:45:10#)` is 2026-03-03 13:45:10). `DateAdd("m", 13, #1/31/2025#)` is 2026-03-03 as well. -- `DateSerial(2026, 2, 31)` is 2026-03-03 in VB6 too, by design: it is not the defect. - -**Expected behavior** -`DateAdd` with the interval `"m"`, `"q"` or `"yyyy"` returns the last day of the target month when the day of the date does not exist in it, as VB6 does and as the VBA documentation says: January 31 plus one month is February 28 (29 in a leap year), and February 29 plus one year is February 28. - -**Desktop:** - - OS: Windows 10 Pro 22H2 (build 19045) - - twinBASIC compiler version: BETA 1005 - -**Additional context** -The same on BETA 997, 995 and 957. Not tried: an exe built from the project, and a project compiled with LLVM. - -Severity: high. The date is wrong and nothing signals it. A program that steps by months or years from a date late in the month (due dates, subscriptions, schedules, a calendar control's next-month button) is up to three days late, and a second step continues from the wrong day: `DateAdd("m", 1, DateAdd("m", 1, #1/31/2026#))` is 2026-04-03, where VB6 gives 2026-03-28. A workaround: `DateSerial(Year(d), Month(d) + n + 1, 0)` is the last day of the month `n` months after `d`, so the result is `DateSerial(Year(d), Month(d) + n, Day(d))` when `Day(d)` is smaller than that day, and that day otherwise. - - - ---- - -## `ReDim` with an upper bound below the lower bound raises *Unspecified error* (-2147467259), where VB6 raises error 9, *Subscript out of range* - -**Describe the bug** -`ReDim` of a dynamic array with an upper bound below its lower bound, such as `ReDim a(0 To -1)`, `ReDim a(1 To 0)` or `ReDim a(5 To 2)`, raises error -2147467259, *Unspecified error* (E_FAIL). VB6 raises error 9, *Subscript out of range*, for each of them. The error is the same for every element type and for `ReDim Preserve`, and a failed `ReDim Preserve` leaves the array as it was. A program that handles error 9, or tests `Err.Number = 9`, around a `ReDim` does not recognise this one. Observed in the IDE, with the error of each statement written to the DEBUG CONSOLE. - -**To Reproduce** -Steps to reproduce the behavior: -1. Open `redim-empty-range-unspecified-error.twinproj` (attached as `redim-empty-range-unspecified-error.zip`). Its one source file, `Startup.twin`, tries 24 `ReDim` statements, each followed by a line that writes the error; the first three are enough: - ``` - Module Startup - - Public Sub Main() - Dim a() As Long - On Error Resume Next - ReDim a(0 To -1) - Debug.Print Err.Number & vbTab & Err.Description - Err.Clear - ReDim a(5 To 2) - Debug.Print Err.Number & vbTab & Err.Description - Err.Clear - ReDim a(0 To 0) - Debug.Print Err.Number & vbTab & Err.Description - End Sub - - End Module - ``` -2. Run the project in the IDE (F5) and read the DEBUG CONSOLE. -3. See `-2147467259 Unspecified error` twice, then `0`. VB6 (the same statements, attached as `redim-empty-range-unspecified-error-vb6.zip`, which writes each result to `out.txt`) gives `9 Subscript out of range` twice, then `0`. - -What decides it, each tried in the project above: -- Every range whose upper bound is below its lower bound raises the error: `(0 To -1)`, `(1 To 0)`, `(5 To 2)`, `(-3 To -5)`; `(-1)`, which is `0 To -1`, and `(n)` with `n = -1`; two dimensions, `(0, 0 To -1)` and `(1 To 0, 0 To 1)`. VB6 raises error 9 for each. -- The element type makes no difference: **Long**, **String**, a user-defined type, **Object**, **Variant** (an array of Variant, and a **Variant** variable, empty or holding an array), and `ReDim s(5 To 2) As String`. -- `ReDim Preserve` raises it too, for `(0 To -1)` of a **Long** array, a **String** array and a **Variant** variable that holds an array, and for `(5 To 2)` of a **Long** array. After the failed `ReDim Preserve a(0 To -1)`, `UBound(a)` is still 2, in both. -- A range of one element or more is not affected: `(0 To 0)`, `(2 To 2)`, `(1 To 2)` and `(0)` raise nothing, in both. -- A 64-bit program, and an exe built from the project, give the same errors. -- With LLVM (the project compiled with `+llvm`) the results differ, and differ from VB6: a range that ends one below its start (`0 To -1`, `1 To 0`, `(-1)`, `(n)`, `(0, 0 To -1)`, `(1 To 0, 0 To 1)`, and the same for **String**, a user-defined type, **Object** and **Variant**) makes an empty array and raises nothing, so `UBound(a)` is `-1`; a range further below (`5 To 2`, `-3 To -5`) raises -2147024882, *Application-defined or object-defined error*; and `ReDim Preserve a(5 To 2)` raises error 9, as VB6 does. - -**Expected behavior** -Error 9, *Subscript out of range*, for each of these statements, as in VB6, whether the code is compiled with LLVM or not. - -**Desktop:** - - OS: Windows 10 Pro 22H2 (build 19045) - - twinBASIC compiler version: BETA 1005 - -**Additional context** -The same on BETA 997, 995 and 957 (not tried with LLVM there). Not tried: the run-time error panel for an unhandled error, and the exe built with LLVM. - -Severity: low. An error is raised either way, and an unhandled one stops the program either way; only its number and text differ, so code that tests for error 9 around a `ReDim` (a size computed from data that can be zero or negative) does not recognise it. With LLVM the statement can make an empty array where VB6 raises an error, which a program written for VB6 does not expect. - - - ---- - -## A project that references the Microsoft Script Control crashes the compiler when it is built for 64-bit programs - -**Describe the bug** -A project with a reference to *Microsoft Script Control 1.0* (`msscript.ocx`) crashes the compiler when its target is win64, although no code uses the reference. The IDE restarts the compiler on the same project (`restarting from MEMORY [...]` in the DEBUG CONSOLE, four times), it crashes again each time, and the IDE ends in Safe Mode, so nothing in the project compiles. With the target win32 the same project compiles with no diagnostic. A reference to the Script Control in an embedded package does the same to every project that embeds the package: the contributed package TBMANLIB 1.0.0.5, which references it, takes down a project of one `Debug.Print`. Observed in the IDE. - -**To Reproduce** -Steps to reproduce the behavior: -1. Open `script-control-reference-win64-crash.twinproj` (attached as `script-control-reference-win64-crash.zip`). Its code is one module that prints `hello`; the project's references, in `Settings`, include - ``` - { - "id": "{0E59F1D2-1FBE-11D0-8FF2-00A0D10038BC}", - "lcid": 0, - "name": "Microsoft Script Control 1.0", - "path32": "C:\\Windows\\SysWOW64\\msscript.ocx", - "path64": "", - "symbolId": "MSScriptControl", - "versionMajor": 1, - "versionMinor": 0 - } - ``` - (the reference as TBMANLIB 1.0.0.5 holds it). -2. Choose **win64** in the target list of the toolbar, or let the IDE compile it for win64 in any way (`tbbuild --arch win64`). -3. See the DEBUG CONSOLE: `restarting from MEMORY []` four times, then the IDE in Safe Mode. With **win32** chosen the project compiles, 0 errors and 0 warnings. - -Each of these was a project of its own, one `Debug.Print "hello"` and the references of the project above, compiled for win64: - -| the project's extra reference | result | -|---|---| -| none | compiles, and builds | -| Script Control, `path64` empty (as above) | the compiler crashes | -| Script Control, `path64` the 32-bit file `C:\Windows\SysWOW64\msscript.ocx` | the compiler crashes | -| Script Control, `path64` a file that does not exist | the compiler crashes | -| Script Control in an embedded package of the project (`Packages/ScLib`, `path64` empty), none in the project | the compiler crashes | -| Common Dialog Control 6.0 (`comdlg32.ocx`), also 32-bit only, `path64` empty or the 32-bit file | compiles | -| Winsock Control 6.0 (`mswinsck.ocx`), also 32-bit only, `path64` empty or the 32-bit file | compiles | -| Microsoft Scripting Runtime, or ADODB 2.8, with `path64` empty | compiles, and builds | -| Microsoft Scripting Runtime with `path64` empty, in an embedded package | compiles, and builds | - -An empty `path64` is not the cause, then: the Script Control crashes the compiler whatever `path64` holds, and two other 32-bit-only controls do not. - -**Expected behavior** -A compiled project, or a diagnostic that names the reference and says that it has no 64-bit component, and a compiler that keeps running. - -**Desktop:** - - OS: Windows 10 Pro 22H2 (build 19045) - - twinBASIC compiler version: BETA 1005 - -**Additional context** -The compiler crashes on BETA 1005, 997, 995 and 987, and does not on 983, 975 and 957. Not tried: BETA 964, 973, 974 and 982, the builds between 983 and 987 (not installed), a `path64` that names a 64-bit build of the control (there is none on this machine), the project built with LLVM, and the exception text (the DEBUG CONSOLE of the crashing IDE holds the `restarting from MEMORY` lines only). - -Severity: high for a project that has the reference. It cannot be compiled for 64-bit programs at all, the IDE ends in Safe Mode, and nothing says which reference is the cause. No wrong code is produced. In a 32-bit program the reference is harmless. A 64-bit program cannot use the control, which exists only as a 32-bit component, so the workaround is to remove the reference. - - - ---- - -## A variable declared as the class of an ActiveX control cannot hold the control: `Set` raises *No such interface supported*, and every member of a `Dim ... As New` variable raises error 91 - -**Describe the bug** -An object variable whose declared type is the class of an ActiveX control, such as `MSScriptControl.ScriptControl`, never receives the object. `Set sc = New MSScriptControl.ScriptControl` and `Set sc = CreateObject("MSScriptControl.ScriptControl")` raise error -2147467262, *No such interface supported* (E_NOINTERFACE). `Dim sc As New MSScriptControl.ScriptControl` raises nothing at the declaration, `TypeName(sc)` is `ScriptControl` and `sc Is Nothing` is False, and then the first member that is used raises error 91, *Object variable or With block variable not set*. The control itself is created and has its interface: the same `New` and `CreateObject` give a working object when the variable is declared `As Object` or `As MSScriptControl.IScriptControl`, the control's default interface. The same happens with the Winsock and Common Dialog controls. The classes of libraries that are not controls (`Scripting.Dictionary`, `ADODB.Connection`) are not affected. VB6 creates every one of these. Observed in the IDE, with the error of each statement written to the DEBUG CONSOLE. - -**To Reproduce** -Steps to reproduce the behavior: -1. Open `activex-control-class-variable.twinproj` (attached as `activex-control-class-variable.zip`). It references Microsoft Script Control 1.0, Microsoft Winsock Control 6.0, Microsoft Common Dialog Control 6.0, Microsoft Scripting Runtime and ADO 2.8 (all 32-bit, so the project's target stays win32), and its module `Startup` runs 21 statements with a line after each that writes the error. Four of them: - ``` - Dim sc As New MSScriptControl.ScriptControl - sc.Language = "JScript" ' error 91 - Dim sc2 As MSScriptControl.ScriptControl - Set sc2 = New MSScriptControl.ScriptControl ' -2147467262, No such interface supported - Dim o As Object - Set o = New MSScriptControl.ScriptControl ' no error - Dim i As MSScriptControl.IScriptControl - Set i = New MSScriptControl.ScriptControl ' no error, and i.Eval("1 + 2") is 3 - ``` -2. Run the project in the IDE (F5) and read the DEBUG CONSOLE. -3. See, for the statements of the project (each is one line in the console, with its error number and text): - - | statement | twinBASIC | VB6 | - |---|---|---| - | `Dim sc As New ScriptControl`, then `sc.Language = "JScript"` or `sc.Eval("1 + 2")` | 91 | no error, `Eval` is 3 | - | `Private ModuleSc As New ScriptControl` at module level, then `ModuleSc.Language = ...` | 91 | no error | - | `Public Sc As New ScriptControl` as a class field, then `h.Sc.Language = ...` | 91 | no error | - | `Set sc = New ScriptControl`, `sc` declared `As ScriptControl` | -2147467262, No such interface supported | no error | - | `Set sc = CreateObject("MSScriptControl.ScriptControl")`, `sc` declared `As ScriptControl` | -2147467262 | no error | - | `Set sc = o`, `o` an **Object** holding the control, `sc` declared `As ScriptControl` | -2147467262 | no error | - | `Set o = New ScriptControl`, and `Set o = CreateObject(...)`, `o` declared `As Object` | no error, `o.Eval("1 + 2")` is 3 | the same | - | `Set i = New ScriptControl`, `i` declared `As IScriptControl` | no error, `i.Eval("1 + 2")` is 3 | the same | - | `Set w = New MSWinsockLib.Winsock`, `w` declared `As Winsock`; the same with `MSComDlg.CommonDialog` | -2147467262 | no error | - | `Dim wa As New MSWinsockLib.Winsock`, then `wa.LocalPort` | 91 | no error | - | the same two classes, the variable declared `As Object` | no error | no error | - | `Dim d As New Scripting.Dictionary`, `Dim c As New ADODB.Connection`, then a member | no error | no error | - - VB6 is the same statements (attached as `activex-control-class-variable-vb6.zip`, which writes each result to `out.txt`). - -What decides it, each tried in the project above: -- The variable's declared type decides: a class that is an ActiveX control (the coclass has the `control` flag in its type library), as a variable's type, fails; `Object`, the default interface and a class that is not a control work. `Dim x As MSScriptControl.DScriptControlSource`, the events interface the class lists beside its default one, fails with the same error on `Set x = o`. -- `New` and `CreateObject` make no difference: `Set o = New ...` and `Set o = CreateObject(...)` both work for an `Object`. -- `Dim ... As New` hides the failure until a member is used; `Set` reports it at the statement. `TypeName` and `Is Nothing` do not show it. -- A built exe gives the same results as a run in the IDE (the statements written with `TbRun.Out` in a scratch copy). -- `Dim j As New MSScriptControl.IScriptControl` is refused with TB5121, *Unable to create instance of type 'IScriptControl'*, so the interface type is usable with `Set` only. - -**Expected behavior** -A variable declared as the class of an ActiveX control holds the control, as in VB6: `Set sc = New MSScriptControl.ScriptControl` and `Dim sc As New MSScriptControl.ScriptControl` give a usable object, and `sc.Eval("1 + 2")` is 3. - -**Desktop:** - - OS: Windows 10 Pro 22H2 (build 19045) - - twinBASIC compiler version: BETA 1005 - -**Additional context** -The same on BETA 997, 995 and 957. The type libraries of the failing classes list two implemented interfaces flagged `default`: the dual interface (`IScriptControl`) and, flagged `default` and `source`, the events dispinterface (`DScriptControlSource`). `ADODB.Connection` lists the same pair and works, so the pair alone does not explain it; the three classes that fail are flagged `control` in their type libraries, and the two that work are not. - -Not tried: a control that is registered for 64-bit, and a form that hosts the control (a variable of the control's class on a form is the usual use). - -Severity: medium. A visible error is raised, never a wrong value, but it is raised at the first use with a message that points at an object that was never set, and a VB6 program that holds an ActiveX control's class in a typed variable, or declares one `As New`, must be changed to `As Object` or to the interface. The contributed package TBMANLIB 1.0.0.5 does this for the Script Control (`Dim MSSC As New MSScriptControl.ScriptControl`), and both functions of its **ToolsJScript** module fail with error 91 for that reason. - - - ---- - ## `Private Sub New` does not make construction private: `New` from another module, or from a project that references the class's package, compiles and runs the constructor **Describe the bug** @@ -4466,723 +3595,3 @@ The page throws `TypeError: Cannot read properties of undefined (reading '0')` i Severity: low. Nothing is lost; the empty designer is misleading. - ---- - -## `QueryInterface` of an object of a twinBASIC class returns E_NOINTERFACE and leaves its out pointer as it was, where COM sets it to NULL - -**Describe the bug** -`IUnknown::QueryInterface`, called through slot 0 of the vtable of an object of a class that twinBASIC compiles (a class of the project, a form, a control), asks for an interface the object does not have and returns `E_NOINTERFACE` (`&H80004002`), but leaves the out pointer, `*ppvObject`, as it was. The COM contract is that the method sets `*ppvObject` to NULL when it fails. A variable set to 12345 before the call is still 12345 after it. Observed in the DEBUG CONSOLE, by calling slot 0 through an interface that the project declares, and again with `DispCallFunc` on the object's own pointer. - -**To Reproduce** -Steps to reproduce the behavior: -1. Open `queryinterface-leaves-out-pointer.twinproj` (attached as `queryinterface-leaves-out-pointer.zip`). Its module `Startup` asks seven objects for an interface that none of them has, and sets the out variable to 12345 before each call. -2. Press F5. Each object is asked twice: through `IRawUnknown`, an interface of the project that has `IUnknown`'s interface identifier and `Extends Nothing`, so that its first method is slot 0 and `[PreserveSig]` returns the raw HRESULT; and with `DispCallFunc`, calling slot 0 of the pointer that `ObjPtr` gives for the `Object` variable. -3. See the DEBUG CONSOLE. For `Plain`, `Implementer` (a class that implements an interface), `Form1` and `CommandButton` (a control added to the form with `Controls.Add`), both calls return `E_NOINTERFACE` and leave `out=12345`. Here is the line for `Plain`, and the line for the `Collection`, which sets the variable to 0 as COM requires: - -``` -Plain: missing interface: hr=80004002 out=12345; with DispCallFunc: hr=80004002 out=12345; IUnknown: hr=0 out is a pointer=True; IDispatch: hr=0 out is a pointer=True -Collection: missing interface: hr=80004002 out=0; with DispCallFunc: hr=80004002 out=0; IUnknown: hr=0 out is a pointer=True; IDispatch: hr=0 out is a pointer=True -``` - -The narrowed reproduction is that project cut down to one object and one call: -```vb -[InterfaceId("00000000-0000-0000-C000-000000000046")] -Private Interface IMin Extends Nothing - [PreserveSig] - Function QueryInterface(ByVal priid As LongPtr, ByRef ppvObject As LongPtr) As Long -End Interface - -Private Class Plain - Public Value As Long -End Class - -Module Startup - - Private Type Guid128 - Data1 As Long - Data2 As Integer - Data3 As Integer - Data4(0 To 7) As Byte - End Type - - Public Sub Main() - Dim u As IMin = New Plain - Dim iid As Guid128 ' an interface that Plain does not have - iid.Data1 = &H12345 - Dim p As LongPtr = 12345 - Dim hr As Long = u.QueryInterface(VarPtr(iid), p) - Debug.Print Hex$(hr), p ' prints 80004002 and 12345; COM requires 0 - End Sub - -End Module -``` - -**Expected behavior** -`*ppvObject` is 0 after every call that fails, so that every line of the output shows `out=0` in place of `out=12345`, as the line for the `Collection` does. The Windows documentation of `IUnknown::QueryInterface` says that `*ppvObject` is set to NULL when the object does not support the interface. VB6 does it for its own classes: its `Class` instance, `Collection`, `Err` and a `Scripting.Dictionary` all return `E_NOINTERFACE` and set the variable to 0 (the VB6 project is attached as `queryinterface-leaves-out-pointer-vb6.zip`; it calls slot 0 with `DispCallFunc`, as VB6 cannot declare the interface). - -**Desktop:** - - OS: Windows 10 Pro 22H2 (build 19045) - - twinBASIC compiler version: BETA 1005 - -**Additional context** -Which objects: the pointer is left as it was for an object of a class that twinBASIC compiles: a plain class, a class that implements an interface of the project, a form, the VB package's `CommandButton`, and (in another project, not attached) the `WebView2` control on a form. It is set to 0, correctly, for the objects of the runtime library: `Collection` and `Err` in the attached project, and `App`, `Screen` and a form's `Controls` in the other. A `Scripting.Dictionary` from the Scripting Runtime library sets it to 0 too. - -What does not reproduce it: asking for an interface the object has. `IUnknown` and `IDispatch` return `S_OK` and write a new pointer, for every object above (the `IUnknown` and `IDispatch` columns of the output). The result is the same through the project's own interface and through `DispCallFunc`, so it is not a side effect of the declaration, and the same in a 32-bit and a 64-bit run. - -Severity: low. The failure is reported, by the HRESULT, so a caller that tests it is not affected. A caller that tests the pointer instead, as a C or C++ client may, sees the value it passed in. When the variable is reused after an earlier successful call, that value is a pointer the caller has released. - - - -## A `For Each` that cannot get an enumerator never ends under `On Error Resume Next` - -**Describe the bug** -A `For Each` whose *group* gives no enumerator --- `Nothing`, an object of a class with no `[Enumerator]` member, or an object whose `[Enumerator]` member returns `Nothing` or raises an error --- never ends when it runs under `On Error Resume Next`, or under an error handler that ends with `Resume Next`. The error in the `For Each` line resumes into the loop body; then `Next` raises error 91, *Object variable or With block variable not set*, and resumes into the body again, without end. The expression after `In` is evaluated once only. Nothing is reported, and the program stops responding. - -**To Reproduce** -Steps to reproduce the behavior: -1. Open `for-each-resume-next-loops.twinproj` (attached as `for-each-resume-next-loops.zip`). Its module `Startup` runs five loops under `On Error Resume Next` and four under a handler that ends with `Resume Next`. Each counts how often the expression after `In` is evaluated (`header=`) and how often the body runs, and leaves the loop after six passes so that the program ends. -2. Press F5. -3. See the DEBUG CONSOLE. The control, a `Collection` of one item, runs once. Every other loop is evaluated once and runs its body until the guard ends it, with error 91 at each pass: - -``` -Collection (control): header=1 body=1 errors: 0 | after Next: 0 -Nothing: header=1 body=6 errors: 91 91 91 91 91 91 | after Next: 0 -Class with no enumerator: header=1 body=6 errors: 91 91 91 91 91 91 | after Next: 0 -Enumerator returns Nothing: header=1 body=6 errors: 91 91 91 91 91 91 | after Next: 0 -Enumerator raises error 5: header=1 body=6 errors: 91 91 91 91 91 91 | after Next: 0 -Nothing, handler with Resume Next: header=1 body=4 passes: error -2147467259 error 91 body error 91 body error 91 body error 91 body error 91 -``` - -The narrowed reproduction: -```vb -Module Startup - Public Sub Main() - Dim o As Object, v As Variant, n As Long - On Error Resume Next - For Each v In o ' o is Nothing: an error, resumed - n = n + 1 - If n = 1000 Then Exit For ' without this line the loop never ends - Next - Debug.Print n ' prints 1000; VB6 prints 1 - End Sub -End Module -``` - -**Expected behavior** -The loop ends. VB6 runs the body once, then raises error 92, *For loop not initialized*, at `Next`, and `Resume Next` continues after the loop. Every loop of the attached VB6 project (`for-each-resume-next-loops-vb6.zip`, the same cases, with classes whose `NewEnum` has the procedure identifier -4) prints `body=1`, then error 92: - -``` -Nothing: header=1 body=1 errors: 424 | after Next: 92 -Class with no enumerator: header=1 body=1 errors: 438 | after Next: 92 -Enumerator returns Nothing: header=1 body=1 errors: 451 | after Next: 92 -Enumerator raises error 5: header=1 body=1 errors: 5 | after Next: 92 -``` - -**Desktop:** - - OS: Windows 10 Pro 22H2 (build 19045) - - twinBASIC compiler version: BETA 1005 - -**Additional context** -The same in a 32-bit and a 64-bit run. A package can meet it without the program's knowledge: the contributed package ArrayListLib returns `Nothing` from its enumerator for a list with no items, and its `ToString` method goes through a list with `For Each` under `On Error Resume Next`, so `ToString` of an empty list never returns. - -The first error also differs from VB6's, which the handler cases show (VB6 in parentheses): `Nothing` raises -2147467259, `E_FAIL` (VB6 424, *Object required*); an enumerator that returns `Nothing` raises -2147467259 (VB6 451, *Property let procedure not defined and property get procedure did not return an object*); an error 5 raised in the enumerator arrives as -2147352567, *Exception occurred* (VB6 5); a class with no enumerator raises 438 in both. - -Severity: high. The program stops responding with no error, in code written to go on after an error, and a program that loops over what a package returns cannot see it coming. - - - -## An assignment through a default member with an argument, written on a public field of an object, is refused with TB5090 - -**Describe the bug** -`obj.Field(key) = value`, where `Field` is a public field of a class and holds an object whose class has a default member that takes an argument, is refused with TB5090, *named argument already provided for ''*. The default member is a `[DefaultMember]` `Property Get` and `Property Let` pair of a class of the project, or the `Item` property of a `Scripting.Dictionary`. The message names nothing, and the line is a plain assignment. The same assignment compiles through a local variable, a module variable (qualified or not), a property that returns the object, an `Object` variable, and `Me.Field(key)` or `Field(key)` inside the class that holds the field. The read `obj.Field(key)` compiles, and so does the assignment with the default member written out, `obj.Field.Item(key) = value`. So the compiler refuses the one place where the object is reached through a field of an instance. - -**To Reproduce** -Steps to reproduce the behavior: -1. Open `field-default-member-named-argument.twinproj` (attached as `field-default-member-named-argument.zip`). Its one source file, `Startup.twin`, has a class `Bag` with a default `Item(Key)`, a class `Holder` with public fields, and `Main` with the assignments; the lines that are refused carry the comment `REFUSED`. -2. Let the IDE compile it (it does so on opening). -3. See five errors, one at each line marked `REFUSED`, and no other: `TB5090 named argument already provided for ''`. - -The narrowed reproduction: -```vb -Class Bag - Private mValue As Long - - [DefaultMember] - Property Get Item(ByVal Key As String) As Long - Item = mValue - End Property - - [DefaultMember] - Property Let Item(ByVal Key As String, ByVal Value As Long) - mValue = Value - End Property -End Class - -Class Holder - Public Field As New Bag -End Class - -Module Startup - Public Sub Main() - Dim h As New Holder - h.Field("a") = 1 ' TB5090 - h.Field.Item("a") = 1 ' compiles - End Sub -End Module -``` - -What the refusal depends on, each tried in a project of its own or as a line in one project: -- the type of the field: `As New Bag`, `As Bag`, `As Object`, `As New Scripting.Dictionary` and `As New Collection` are all refused (a `Collection` has no way to assign an item, so it is no use as a comparison); it is the field, not its type; -- how the object holding the field is reached: a variable `As Holder`, `As New Holder`, a parameter, a `With` block and the left-hand side of a compound statement (`h.Field("a") = h.Field("a") + 1`) are refused; `Me.Field(...)` and a bare `Field(...)` inside the class, and a variable `As Object` or `As Variant` that holds the `Holder`, compile; -- a `Property Get Prop() As Bag` in the place of the field: `h.Prop("a") = 1` compiles; a field of a user-defined type that holds a `Bag` (`u.UdtBag("a") = 1`) compiles; a module's public variable, written `Startup.PubBag("a") = 1`, compiles; -- `Set h.ObjField("a") = x`, with a default `Property Set Item(Key)`, is refused too, with another message: TB5001, *Argument for '': cannot coerce type 'String' to '_ObjBag'* (the key is taken for the object the default member returns). - -**Expected behavior** -The assignment compiles and calls the default member's `Property Let` (or `Property Set`), as it does through a property that returns the same object. VB6 does not compile it either: its build stops with the compile error *Wrong number of arguments or invalid property assignment* on each of the five refused lines, and on `Me.Field("a") = 1` inside the class, which twinBASIC compiles. VB6 does compile the property, the local variable, the read, and the late-bound object (the attached VB6 project, `field-default-member-named-argument-vb6.zip`, builds those and lists the refused lines as comments). So the request is for twinBASIC to be consistent with itself, and, if the refusal stays, a message that says why: the one it gives names an argument that does not exist. - -**Desktop:** - - OS: Windows 10 Pro 22H2 (build 19045) - - twinBASIC compiler version: BETA 1005 - -**Additional context** -The same in a 32-bit and a 64-bit compile. Met in the contributed package TBMANLIB, whose `cHttpClient` has a public `RequestHeaders` field that holds a dictionary: `Http.RequestHeaders("X-Key") = "1"` is refused and `Http.RequestHeaders.Item("X-Key") = "1"` is accepted. - -Severity: low. The line is refused at compile time and the default member written out is a workaround. - - - -## Empty parentheses after a function result or a variable are accepted on a typed value and fail at run time on a Variant - -**Describe the bug** -A pair of empty parentheses written after a function's result or a variable, as in `Num = Asc(c)()` or `G = Chr(Num)()`, compiles. What it does when it runs depends on the type of the value in front of it. When the type is not `Variant` (`Long`, `Double`, `String`), the parentheses are ignored and the value is used: `Asc("abc")()` is 97. When it is a `Variant` (the result of `Chr`, `Mid`, `Left`, `Int`, `Sgn`, `Abs` and `InStr`, of a `Function ... As Variant` of the project, or a `Variant` variable), the line raises error -2147467259, *Unspecified error*. When it is an object, the default member is called with no arguments and raises error -2147352561, *Parameter not optional* if it needs one. The compiler gives no diagnostic for any of these. - -**To Reproduce** -Steps to reproduce the behavior: -1. Open `empty-parentheses-after-value.twinproj` (attached as `empty-parentheses-after-value.zip`). Its module `Startup` runs nine such assignments under `On Error Resume Next` and prints each result and error number. -2. Press F5. -3. See the DEBUG CONSOLE: the six lines with a value that is not a `Variant` give their value and error 0; the three with a `Variant` give an empty result and error -2147467259: - -``` -Asc(s)() [Long]: result=[97] error 0 -Len(s)() [Long]: result=[3] error 0 -Twice(3)() [Long]: result=[6] error 0 -n() [Long variable]: result=[5] error 0 -s() [String variable]: result=[abc] error 0 -Chr$(65)() [String]: result=[A] error 0 -Chr(65)() [Variant]: result=[] error -2147467259 -VarLong()() [Variant]: result=[] error -2147467259 -v() [Variant variable]: result=[] error -2147467259 -``` - -What the compiler does refuse: arguments in the second pair (`Len(s)(1)` and `Twice(3)(1)`, TB5030, *Unexpected call arguments*), `(Asc(s))()` (TB5001, *expected a delegate*), and `Coll()()` for a function that returns a `Collection` (TB5092, *Missing argument 'Index'*). - -**Expected behavior** -A diagnostic for the value that is not a `Variant`, as VB6 gives: its build stops with *Type mismatch* on `Asc(s)()`, `Twice(3)()` and `Chr$(65)()`, *Syntax error* on `Len(s)()`, and *Expected array* on `n()` and `s()`. For a `Variant`, VB6 compiles the line and raises error 9, *Subscript out of range*, when it runs (the parentheses are read as an index of an array with no subscripts); twinBASIC raises -2147467259 (the attached VB6 project, `empty-parentheses-after-value-vb6.zip`, builds the three `Variant` lines, and lists the refused ones with VB6's messages as comments). - -**Desktop:** - - OS: Windows 10 Pro 22H2 (build 19045) - - twinBASIC compiler version: BETA 1005 - -**Additional context** -The same in a 32-bit and a 64-bit run. Met in the contributed package TBMANLIB, whose module `ToolsCN` has `Num = Asc(Mid(CnChar, i, 1))()` and `G = Chr(Num)()` in `GetPinYin`: the first line runs and returns the character code, the second raises the error, for every ASCII character. - -Severity: low. Code that VB6 refuses is accepted and, for the types that are not `Variant`, works; for a `Variant` it raises an error that does not say what is wrong. - - - -## An assignment to an element of a public array field of a class crashes the compiler - -**Describe the bug** -`h.Values(1) = 5`, where `Values` is a public array field of a class (`Public Values(1 To 3) As Long`) and `h` is a variable of that class, crashes the compiler. The IDE prints `NATIVE EXCEPTION: ACCESS_VIOLATION` in the DEBUG CONSOLE, restarts the compiler on the same project, and it crashes again, three times in a run, so nothing in the project compiles while the line is there. The declaration alone does not crash it, and nor does the read of an element, which is refused with a diagnostic (TB5033, *Binding error, no datatype*), so the assignment is the one use that crashes the compiler instead of being compiled or refused. - -**To Reproduce** -Steps to reproduce the behavior: -1. Open `class-array-field-element-assignment-crash.twinproj` (attached as `class-array-field-element-assignment-crash.zip`). Its one source file, `Startup.twin`, holds the whole bug: - ``` - Class Holder - Public Values(1 To 3) As Long - End Class - - Module Startup - - Public Sub Main() - Dim h As New Holder - h.Values(1) = 5 - End Sub - - End Module - ``` -2. Let the IDE compile it (it does so on opening). -3. See the DEBUG CONSOLE: `NATIVE EXCEPTION: ACCESS_VIOLATION {no-basic-code} [$1EAFA348:twinBASIC_win32.dll+000FA348]`, then `restarting from MEMORY [...]`, and the same exception twice more, each with a list of the compiler's threads in which the failing one is `>>> thread 0004: ResolveStageZ_CompilationsUnitStart, /ClassArrayFieldElementAssignmentCrash/Sources/Startup.twin; within unit Main; starting line 12`. Without the line `h.Values(1) = 5` the project compiles without a diagnostic. - -What it depends on, each tried in a project of its own: -- the assignment crashes with the array bounds `(1 To 3)` and `(3)`, with `h` declared `As New Holder` or `As Holder`, and in a `With h` block (`.Values(1) = 5`); the class having other members does not change that (a `Sub`, or a field `Public X As Long`, with which the compiler also reports TB5131 at the declaration, as the next paragraph says); -- it compiles when `h` is an `Object` variable that holds the `Holder` (late bound), when the assignment is inside the class, for an array of a user-defined type (`u.A(1) = 5`), and for a public array of a module (`Startup.A(1) = 5`); -- a dynamic array field, `Public Values() As Long`, is refused instead of crashing: `ReDim h.Values(1 To 3)` gives TB5063 and `h.Values(1) = 5` gives TB5001, *cannot coerce type 'Integer' to [ByRef] 'Long()'*; so a public array field of a class cannot be indexed from outside the class at all. - -The declaration is also treated unevenly. TB5131, *class modules cannot expose public fixed-arrays*, is reported at `Public Values(1 To 3) As Long` when the class has a field of another type (`Public X As Long` before or after it), and not when the array is the class's only field, nor when the class has only arrays (two of them) or only arrays and methods. The crash is the same with and without TB5131. - -**Expected behavior** -A diagnostic, or a compiled project. The assignment is refused the way the read of an element is, with a message that says what is wrong, and the project keeps compiling. VB6 refuses the declaration itself: its build stops with *Constants, fixed-length strings, arrays, user-defined types and Declare statements not allowed as Public members of object modules* (the attached VB6 project, `class-array-field-element-assignment-crash-vb6.zip`, has the line as a comment, with an indexed property in its place). - -**Desktop:** - - OS: Windows 10 Pro 22H2 (build 19045) - - twinBASIC compiler version: BETA 1005 - -**Additional context** -The same in a 32-bit and a 64-bit compile. Met while documenting the contributed package TBMANLIB: a probe project with a class that had a public fixed array, and a statement that assigned to one of its elements, crashed the compiler. - -Severity: high for a project that has the line. The compiler crashes on every attempt and restarts into the same crash, so the IDE reports nothing else about the project, and the line has to be found and removed by hand. No wrong code is produced. - - - ---- - -## Setting a Slider's `BackColor` at run time does not change what the control shows, even after the window is invalidated - -**Describe the bug** -The Slider of the WinNativeCommonCtls package keeps drawing its old background after `BackColor` is set at run time. The new colour appears only after the control is hidden and shown again. Invalidating the window (`InvalidateRect` and `UpdateWindow`) does not make it appear. Observed by reading a pixel of the control's window with `GetPixel`, and by eye. -The Slider hands out its `BackColor` only in its handler of the `WM_CTLCOLORSTATIC` message (`HandleGetColors`, in `CONTROLS/Slider.twin`), and has no handler for the property being set. The ProgressBar and the ListView of the same package have one (`BackColorChanged`, `Handles BackColor.OnPropertyLet`), which validates the colour and repaints. - -**To Reproduce** -Steps to reproduce the behavior: -1. Open `slider-backcolor-no-repaint.twinproj` (attached as `slider-backcolor-no-repaint.zip`). `Form1` holds one Slider, `Slider1`, with a design-time `BackColor` of `&H808080`. -2. Run the project (F5). `Sub Main` shows `Form1`, lets it paint, and then does: - ``` - Form1.Slider1.BackColor = vbRed - Settle ' DoEvents and Sleep 25, twenty times - Debug.Print "BackColor set to &H" & Hex$(Form1.Slider1.BackColor) & ", pixel = &H" & Hex$(Corner()) - ``` - where `Corner` is `GetPixel` on `GetDC(Form1.Slider1.hWnd)` at (2, 2), a point outside the channel and the thumb. It then calls `InvalidateRect` and `UpdateWindow` on the Slider's window and reads the pixel again, and last sets `Visible` to `False` and back to `True` and reads it a third time. -3. See in the DEBUG CONSOLE: - ``` - painted, BackColor = &H808080, pixel = &H808080 - BackColor set to &HFF, pixel = &H808080 - after InvalidateRect and UpdateWindow, pixel = &H808080 - after Visible = False, True, pixel = &HFF - ``` - -**Expected behavior** -The Slider shows `BackColor` as soon as it is set: the second line ends `pixel = &HFF`. A control that is invalidated draws its current `BackColor`, so the third line does too. The ProgressBar and ListView of the same package repaint when their `BackColor` is set. - -**Desktop:** - - OS: Windows 10 Pro 22H2 (build 19045) - - twinBASIC compiler version: BETA 1005 - -**Additional context** -`GetPixel` reads what is on screen. `PrintWindow` is not used because it makes the window paint itself, which would show the new colour and hide the defect. -Tried in a scratch variant, not kept: `RedrawWindow` with `RDW_INVALIDATE Or RDW_ERASE Or RDW_UPDATENOW Or RDW_ALLCHILDREN`, and a change of `Slider1.Value`, left the pixel at `&H808080` too. Minimising and restoring the form, and resizing it, were tried only after the hide and show, so they show nothing about this. -Severity: low. The colour is wrong until the control is hidden and shown, and nothing is lost. -VB6 was not tried. - - - ---- - -## `ListItem.BackColor` raises error 380 when it is assigned its own default, -1, so a row that has a colour cannot return to the list's colour - -**Describe the bug** -The `BackColor` of a ListItem (WinNativeCommonCtls) defaults to `-1`, which means "use the ListView's `BackColor`" (`SUPPORT/ListItem.twin`: `Public BackColor As OLE_COLOR = -1`, with the comment `CLR_NONE`). Assigning `-1` to a row that has been given a colour raises error 380, *Invalid property value*. The row keeps its colour, so once a row has a colour of its own there is no way back to the list's colour. -The cause is `BackColorChanged`, which calls `VB.CommonValidateColorOrRevert`. In BETA 1005 that function refuses `-1` (the change that made every colour property of the VB controls refuse a value that is not a colour); `-1` is the one value this property uses on purpose. Assigning `-1` to a new item, which already holds `-1`, raises nothing, because nothing changes. - -**To Reproduce** -Steps to reproduce the behavior: -1. Open `listitem-backcolor-default-refused.twinproj` (attached as `listitem-backcolor-default-refused.zip`). `Form1` holds one ListView, `ListView1`, and the module `ListItemBackColorTests` holds the test cases. -2. Run, with `On Error Resume Next`: - ``` - Load Form1 - Set item = Form1.ListView1.ListItems.Add(, , "row") - item.BackColor = vbYellow - item.BackColor = -1 - Debug.Print Err.Number, item.BackColor - ``` -3. See `380` and `65535`. Expected `0` and `-1`. - -The module below is the reproducer's, and is meant to be adapted as a regression test. On BETA 1005 the first four cases pass and the last fails with `Expected: 0, Actual: 380`. -```vb -[TestFixture] -Module ListItemBackColorTests - - Private Const UseListColour As Long = -1 - - [TestCase] - Public Sub DefaultIsUseListColour() - Dim item As ListItem, value As Long - Load Form1 - Set item = Form1.ListView1.ListItems.Add(, , "row") - value = item.BackColor - Unload Form1 - Assert.Strict.AreEqual UseListColour, value, "the default" - End Sub - - [TestCase] - Public Sub ColourIsAccepted() - Dim item As ListItem, number As Long, value As Long - Load Form1 - Set item = Form1.ListView1.ListItems.Add(, , "row") - On Error Resume Next: item.BackColor = vbYellow: number = Err.Number: On Error GoTo 0 - value = item.BackColor - Unload Form1 - Assert.Strict.AreEqual 0, number, "the error" - Assert.Strict.AreEqual CLng(vbYellow), value, "the colour" - End Sub - - [TestCase] - Public Sub DefaultAssignedToNewItem() - Dim item As ListItem, number As Long, value As Long - Load Form1 - Set item = Form1.ListView1.ListItems.Add(, , "row") - On Error Resume Next: item.BackColor = UseListColour: number = Err.Number: On Error GoTo 0 - value = item.BackColor - Unload Form1 - Assert.Strict.AreEqual 0, number, "the error" - Assert.Strict.AreEqual UseListColour, value, "the colour" - End Sub - - [TestCase] - Public Sub ColourIsKeptWhenDefaultIsAssigned() - Dim item As ListItem, value As Long - Load Form1 - Set item = Form1.ListView1.ListItems.Add(, , "row") - item.BackColor = vbYellow - On Error Resume Next: item.BackColor = UseListColour: On Error GoTo 0 - value = item.BackColor - Unload Form1 - Assert.Strict.AreEqual True, (value = CLng(vbYellow) Or value = UseListColour), "yellow or the list's colour" - End Sub - - [TestCase] - Public Sub DefaultAssignedAfterColour() - Dim item As ListItem, number As Long, value As Long - Load Form1 - Set item = Form1.ListView1.ListItems.Add(, , "row") - item.BackColor = vbYellow - On Error Resume Next: item.BackColor = UseListColour: number = Err.Number: On Error GoTo 0 - value = item.BackColor - Unload Form1 - Assert.Strict.AreEqual 0, number, "the error" - Assert.Strict.AreEqual UseListColour, value, "the colour" - End Sub - -End Module -``` - -**Expected behavior** -Assigning `-1` to `ListItem.BackColor` is accepted and the row is drawn in the ListView's `BackColor` again, as for a new row. `-1` is the property's default and its documented meaning, so the colour check has to let it through for this property (the controls of the VB package have no such value, and refusing it there is right). MSCOMCTL's ListItem has no `BackColor`, so VB6 has nothing to compare. - -**Desktop:** - - OS: Windows 10 Pro 22H2 (build 19045) - - twinBASIC compiler version: BETA 1005 - -**Additional context** -A ListItem's `ForeColor` defaults to a system colour, not to -1, so the same check does no harm there. Severity: low. A program can only give a row a colour that looks like the list's, and the row is not changed by the refused assignment. - - - ---- - -## A call to a `Declare Function` marked `[ConstantFoldable]` crashes the build when Constant Function Folding is on - -**Describe the bug** -With **Constant Function Folding** set to **Yes** in Project Settings (`"optimizer.constantFunctionFolding": true`), a `Declare Function` that carries `[ConstantFoldable]` (or `[ConstantFoldableNumericsOnly]`) and is called with constant arguments crashes the compiler during the build. The project compiles clean (0 errors, 0 warnings); Build then prints `NATIVE EXCEPTION: ACCESS_VIOLATION` in the DEBUG CONSOLE, the IDE restarts the compiler on the project, the same exception follows, and no exe is written. - -**To Reproduce** -Steps to reproduce the behavior: -1. Open `constantfoldable-declare-crash.twinproj` (attached as `constantfoldable-declare-crash.zip`). Its project has Constant Function Folding set to Yes, and its one source file, `Startup.twin`, holds the whole bug: - ``` - Module Startup - - Public Sub Main() - GetCurrentProcessId - End Sub - - [ConstantFoldable] - Declare Function GetCurrentProcessId Lib "kernel32" () As Long - - End Module - ``` -2. Let the IDE compile it: no diagnostic. -3. Build the project (**Build**, or `node scripts/tbbuild.mjs --build`). See the DEBUG CONSOLE: `NATIVE EXCEPTION: ACCESS_VIOLATION {unknown} [$00000000]`, then `restarting from MEMORY [...]` and the same exception again. The build produces no exe. - -What it depends on, each tried in a project of its own: -- it needs the setting on, the attribute and a call: with Constant Function Folding off the project builds; without `[ConstantFoldable]` it builds; with the attribute and no call (`Sub Main` empty) it builds; -- every argument of the call must be a constant, and a call with none counts: `r = DSleepFn(5, 0)` for `Declare Function DSleepFn Lib "kernel32" Alias "SleepEx" (ByVal ms As Long, ByVal a As Long) As Long` crashes, and so does the zero-argument call above. With a variable for either argument (`v = 5: r = DSleepFn(v, 0)`, or `DSleepFn(5, v)`) it builds; -- `[ConstantFoldableNumericsOnly]` crashes as `[ConstantFoldable]` does; `Declare`, `Declare PtrSafe`, `Private Declare` and `DeclareWide` all crash; the call may be a statement or the right side of an assignment; -- a `[ConstantFoldable]` ordinary `Function` in a Module, called with a constant, builds (it is folded, which is what the attribute is for), and so does a `Declare Sub`. - -**Expected behavior** -A built exe. A call to a Declare is not a pure function of its arguments (it calls into a DLL), so it is either not folded, or the attribute is refused on a `Declare Function` with a diagnostic, as it is refused on a method of a class. The compiler does not crash. The attribute does not exist in VB6, and neither does Constant Function Folding. - -**Desktop:** - - OS: Windows 10 Pro 22H2 (build 19045) - - twinBASIC compiler version: BETA 1005 - -**Additional context** -The same in a 32-bit and a 64-bit build. The IDE's compile (what runs when a project is opened or edited) is clean, so the editor shows no sign of the problem; only Build crashes, so a project can hold the line for a long time before it is noticed. -Severity: medium. The build crashes every time, and the compiler restarts into the same crash, but the project compiles and runs from the IDE, and removing the attribute or the setting fixes it. No wrong code is produced. - - - ---- - -## `[RunBeforeStartupObject]` on a generic procedure crashes the build - -**Describe the bug** -`[RunBeforeStartupObject]` on a generic procedure of a Module, `Public Sub Pre(Of T)()`, compiles without a diagnostic and then crashes the compiler during the build. The DEBUG CONSOLE shows `NATIVE EXCEPTION: ACCESS_VIOLATION`, the IDE restarts the compiler on the project, the same exception follows, and no exe is written. - -**To Reproduce** -Steps to reproduce the behavior: -1. Open `runbeforestartupobject-generic-crash.twinproj` (attached as `runbeforestartupobject-generic-crash.zip`). Its one source file, `Startup.twin`, holds the whole bug: - ``` - Module Startup - - Public Sub Main() - End Sub - - [RunBeforeStartupObject] - Public Sub Pre(Of T)() - End Sub - - End Module - ``` -2. Let the IDE compile it: no diagnostic. -3. Build the project. See the DEBUG CONSOLE: `NATIVE EXCEPTION: ACCESS_VIOLATION {no-basic-code} [$1EA2AFE8:twinBASIC_win32.dll+0002AFE8]`, then `restarting from MEMORY [...]` and the same exception again. No exe is written. - -What it depends on, each tried in a project of its own: -- the generic procedure with the attribute alone is the cause: `Pre(Of T)` without the attribute builds, and `Pre()` with it, not generic, builds; -- a generic `Function` crashes too (`Public Function Pre(Of T)() As Boolean`); the body, an empty Sub or Function, does not matter, and nothing calls `Pre`. - -**Expected behavior** -Either a diagnostic that the attribute is not allowed on a generic procedure, or a built exe in which `Pre` runs before `Main` (what it does on a procedure that is not generic). The compiler does not crash. The attribute does not exist in VB6. - -**Desktop:** - - OS: Windows 10 Pro 22H2 (build 19045) - - twinBASIC compiler version: BETA 1005 - -**Additional context** -The same in a 32-bit and a 64-bit build. Only Build crashes: the IDE's compile and the sweep of the attribute's sites find no problem, so the line stays unnoticed until the first build. -Severity: medium. The build crashes every time, and the compiler restarts into the same crash; removing the attribute from the generic procedure fixes it. - - - ---- - -## `[RunAfterBuild]` on a generic Sub is accepted and the Sub never runs - -**Describe the bug** -A Sub marked `[RunAfterBuild]` runs in the IDE once the build has finished. When the Sub is generic (`Public Sub Built(Of T)()`), the attribute is accepted with no diagnostic, the project builds, and the DEBUG CONSOLE says `[BUILD] Executing '...Built'...` as it does for any other such Sub, but the body of the Sub is never run: its `Debug.Print` prints nothing and a file it opens is not written. The compiler does count the Sub as the project's `[RunAfterBuild]`, since a second one is TB5114, *encountered too many [RunAfterBuild] attributes. Only allowed one per-project*. - -**To Reproduce** -Steps to reproduce the behavior: -1. Open `runafterbuild-generic-sub-never-runs.twinproj` (attached as `runafterbuild-generic-sub-never-runs.zip`). Its one source file, `Startup.twin`, holds the whole bug: - ``` - Module Startup - - Public Sub Main() - End Sub - - [RunAfterBuild] - Public Sub Built(Of T)() - Debug.Cls - Debug.Print "built" - End Sub - - End Module - ``` -2. Build the project. -3. See the DEBUG CONSOLE: the build log, ending `[BUILD] Executing 'RunafterbuildGenericSubNeverRuns.Startup.Built'...`, and no line `built` after it. Remove `(Of T)` and build again: `built` is printed (the console is cleared first, so it is the only line). - -Tried in a project of its own: a body that opens a file under `Environ("TEMP")` and writes a line to it, in place of the `Debug.Print`; the file is not created. The same in a 32-bit and a 64-bit build. - -**Expected behavior** -The Sub runs after the build, as it does without `(Of T)`, or the attribute is refused on a generic Sub with a diagnostic (as `[RunAfterBuild]` is refused on a second procedure, TB5114). A Sub that the compiler accepts, counts and announces as executing runs. The attribute does not exist in VB6. - -**Desktop:** - - OS: Windows 10 Pro 22H2 (build 19045) - - twinBASIC compiler version: BETA 1005 - -**Additional context** -`[RunAfterBuild]` on a `Function` and on a `Property Get` in a Module runs as it does on a Sub, in the same project layout. -Severity: low. Nothing crashes and nothing is lost; the code the author expected to run does not, with no message. - - - ---- - -## Some VB6 attribute lines have no effect in a built type library - -**Describe the bug** -twinBASIC reads the `Attribute` lines of a `.cls` file, and the IDE's VB6 import keeps them as VB6 wrote them, but some of them do not reach the type library of the built DLL. `VB_HelpID` and `VB_VarHelpID` leave the class and its members with help context 0. `VB_VarMemberFlags = "40"` and `"400"` leave a public field neither hidden nor nonbrowsable. `VB_MemberFlags = "200"` (user-interface default) and `"14"` (bindable and displaybind) leave the member with no flags. No diagnostic says so. The other attribute lines do take effect in the same build: `VB_Description`, `VB_VarDescription`, `VB_UserMemId`, `VB_VarUserMemId`, `VB_MemberFlags = "40"`, `"400"` and `"440"`, `VB_Creatable`, `VB_GlobalNameSpace`, `VB_PredeclaredId` and `VB_Exposed`. Seen by reading the built DLL's type library with `LoadTypeLibEx`. - -**To Reproduce** -Steps to reproduce the behavior: -1. Unzip `vb6-attribute-lines-no-effect.zip` and open `vb6-attribute-lines-no-effect.twinproj`: an ActiveX DLL with **Register DLL after build** off, so building it registers nothing. `Attrs.cls` holds the lines; `TwinAttrs.twin` is the control. -2. Build it. -3. Open the DLL's type library in OLE/COM Object Viewer (**File → View TypeLib...**). -4. See the coclass `Attrs` with no `helpcontext`, and in `_Attrs`: `HiddenField` and `UnbrowsedField` with neither `hidden` nor `nonbrowsable`, `HelpField` and `WithHelp` with no `helpcontext`, `UiDefault` without `uidefault`, and `Bound` without `bindable` or `displaybind`. -5. See `HiddenMethod`, whose line is `VB_MemberFlags = "40"`, marked `hidden`, and `TwinAttrs.HiddenField`, written with `[Hidden]`, marked `hidden` too. - -**Expected behavior** -Each line gives what it asks for, as its neighbours do: a help context of 2000, 2001 and 2002, a hidden field as `[Hidden]` gives on a field and as `VB_MemberFlags = "40"` gives on a method, a nonbrowsable field, and `FUNCFLAG_FUIDEFAULT`, `FUNCFLAG_FBINDABLE` and `FUNCFLAG_FDISPLAYBIND`. VB6 writes these lines from its Procedure Attributes dialog, so a VB6 component imported into twinBASIC and built again loses its help context IDs, its hidden fields and its data-binding flags without a word. twinBASIC has no attribute of its own for a help context ID, a user-interface default or data binding, so these lines are the only way to ask for them. - -**Desktop:** - - OS: Windows 10 Pro 22H2 (build 19045) - - twinBASIC compiler version: BETA 1005 - -**Additional context** -The import is not the cause: it keeps every `Attribute` line of a `.bas` and a `.cls` byte for byte, and those of a `.frm`, `.ctl` and `.pag` inside the class it writes. The type information a class has at run time in the IDE carries no flags and no descriptions for any member, whichever way they are written, so the built DLL is where to look. `[NonBrowsable]` on a public field leaves it without `nonbrowsable` as well. A VB6 build of the same class was not made: VB6 registers an ActiveX DLL as it builds it. Severity: medium for anyone moving a VB6 component to twinBASIC under binary compatibility; nothing fails, and the loss shows only to the component's users, in their object browsers and data-binding dialogs. - - - ---- - -## A `GoSub` in an exe built with LLVM ends the program with an access violation - -**Describe the bug** -In an exe built with LLVM turned on, a procedure that runs `GoSub