aboutsummaryrefslogtreecommitdiffhomepage
path: root/win32/SurfaceGDI.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'win32/SurfaceGDI.cxx')
-rw-r--r--win32/SurfaceGDI.cxx884
1 files changed, 884 insertions, 0 deletions
diff --git a/win32/SurfaceGDI.cxx b/win32/SurfaceGDI.cxx
new file mode 100644
index 000000000..0fcb99397
--- /dev/null
+++ b/win32/SurfaceGDI.cxx
@@ -0,0 +1,884 @@
+// Scintilla source code edit control
+/** @file SurfaceGDI.cxx
+ ** Implementation of drawing to GDI on Windows.
+ **/
+// Copyright 1998-2003 by Neil Hodgson <neilh@scintilla.org>
+// The License.txt file describes the conditions under which this software may be distributed.
+
+#include <cstddef>
+#include <cstdlib>
+#include <cstdint>
+#include <cstring>
+#include <cstdio>
+#include <cstdarg>
+#include <ctime>
+#include <cmath>
+#include <climits>
+
+#include <string_view>
+#include <vector>
+#include <map>
+#include <optional>
+#include <algorithm>
+#include <iterator>
+#include <memory>
+#include <mutex>
+
+// Want to use std::min and std::max so don't want Windows.h version of min and max
+#if !defined(NOMINMAX)
+#define NOMINMAX
+#endif
+#undef _WIN32_WINNT
+#define _WIN32_WINNT 0x0A00
+#undef WINVER
+#define WINVER 0x0A00
+#define WIN32_LEAN_AND_MEAN 1
+#include <windows.h>
+#include <commctrl.h>
+#include <richedit.h>
+#include <windowsx.h>
+#include <shellscalingapi.h>
+
+#include "ScintillaTypes.h"
+
+#include "Debugging.h"
+#include "Geometry.h"
+#include "Platform.h"
+#include "XPM.h"
+#include "UniConversion.h"
+#include "DBCS.h"
+
+#include "WinTypes.h"
+#include "PlatWin.h"
+#include "SurfaceGDI.h"
+
+using namespace Scintilla;
+using namespace Scintilla::Internal;
+
+// All file hidden in unnamed namespace except for FontGDI_Allocate and SurfaceGDI_Allocate
+namespace {
+
+constexpr Supports SupportsGDI[] = {
+ Supports::PixelModification,
+};
+
+constexpr BYTE Win32MapFontQuality(FontQuality extraFontFlag) noexcept {
+ switch (extraFontFlag & FontQuality::QualityMask) {
+
+ case FontQuality::QualityNonAntialiased:
+ return NONANTIALIASED_QUALITY;
+
+ case FontQuality::QualityAntialiased:
+ return ANTIALIASED_QUALITY;
+
+ case FontQuality::QualityLcdOptimized:
+ return CLEARTYPE_QUALITY;
+
+ default:
+ return DEFAULT_QUALITY;
+ }
+}
+
+void SetLogFont(LOGFONTW &lf, const char *faceName, CharacterSet characterSet, XYPOSITION size, FontWeight weight, bool italic, FontQuality extraFontFlag) {
+ lf = LOGFONTW();
+ // The negative is to allow for leading
+ lf.lfHeight = -(std::abs(std::lround(size)));
+ lf.lfWeight = static_cast<LONG>(weight);
+ lf.lfItalic = italic ? 1 : 0;
+ lf.lfCharSet = static_cast<BYTE>(characterSet);
+ lf.lfQuality = Win32MapFontQuality(extraFontFlag);
+ UTF16FromUTF8(faceName, lf.lfFaceName, LF_FACESIZE);
+}
+
+struct FontGDI : public FontWin {
+ HFONT hfont = {};
+ CharacterSet characterSet = CharacterSet::Ansi;
+ FontGDI(HFONT hfont_, CharacterSet characterSet_) noexcept : hfont(hfont_), characterSet(characterSet_) {
+ // Takes ownership and deletes the font
+ }
+ explicit FontGDI(const FontParameters &fp) : characterSet(fp.characterSet) {
+ LOGFONTW lf;
+ SetLogFont(lf, fp.faceName, fp.characterSet, fp.size, fp.weight, fp.italic, fp.extraFontFlag);
+ hfont = ::CreateFontIndirectW(&lf);
+ }
+ // Deleted so FontGDI objects can not be copied.
+ FontGDI(const FontGDI &) = delete;
+ FontGDI(FontGDI &&) = delete;
+ FontGDI &operator=(const FontGDI &) = delete;
+ FontGDI &operator=(FontGDI &&) = delete;
+ ~FontGDI() noexcept override {
+ if (hfont)
+ ::DeleteObject(hfont);
+ }
+ [[nodiscard]] HFONT HFont() const noexcept override {
+ // Duplicating hfont
+ LOGFONTW lf = {};
+ if (0 == ::GetObjectW(hfont, sizeof(lf), &lf)) {
+ return {};
+ }
+ return ::CreateFontIndirectW(&lf);
+ }
+ [[nodiscard]] std::unique_ptr<FontWin> Duplicate() const override {
+ HFONT hfontCopy = HFont();
+ return std::make_unique<FontGDI>(hfontCopy, characterSet);
+ }
+ [[nodiscard]] CharacterSet GetCharacterSet() const noexcept override {
+ return characterSet;
+ }
+};
+
+class SurfaceGDI : public Surface {
+ SurfaceMode mode;
+ HDC hdc{};
+ bool hdcOwned = false;
+ HPEN pen{};
+ HPEN penOld{};
+ HBRUSH brush{};
+ HBRUSH brushOld{};
+ HFONT fontOld{};
+ HBITMAP bitmap{};
+ HBITMAP bitmapOld{};
+
+ int logPixelsY = USER_DEFAULT_SCREEN_DPI;
+
+ static constexpr int maxWidthMeasure = INT_MAX;
+ // There appears to be a 16 bit string length limit in GDI on NT.
+ static constexpr int maxLenText = 65535;
+
+ void PenColour(ColourRGBA fore, XYPOSITION widthStroke) noexcept;
+
+ void BrushColour(ColourRGBA back) noexcept;
+ void SetFont(const Font *font_);
+ void Clear() noexcept;
+
+public:
+ SurfaceGDI() noexcept = default;
+ SurfaceGDI(HDC hdcCompatible, int width, int height, SurfaceMode mode_, int logPixelsY_) noexcept;
+ // Deleted so SurfaceGDI objects can not be copied.
+ SurfaceGDI(const SurfaceGDI &) = delete;
+ SurfaceGDI(SurfaceGDI &&) = delete;
+ SurfaceGDI &operator=(const SurfaceGDI &) = delete;
+ SurfaceGDI &operator=(SurfaceGDI &&) = delete;
+
+ ~SurfaceGDI() noexcept override;
+
+ void Init(WindowID wid) override;
+ void Init(SurfaceID sid, WindowID wid) override;
+ std::unique_ptr<Surface> AllocatePixMap(int width, int height) override;
+
+ void SetMode(SurfaceMode mode_) override;
+
+ void Release() noexcept override;
+ int SupportsFeature(Supports feature) noexcept override;
+ bool Initialised() override;
+ int LogPixelsY() override;
+ int PixelDivisions() override;
+ int DeviceHeightFont(int points) override;
+ void LineDraw(Point start, Point end, Stroke stroke) override;
+ void PolyLine(const Point *pts, size_t npts, Stroke stroke) override;
+ void Polygon(const Point *pts, size_t npts, FillStroke fillStroke) override;
+ void RectangleDraw(PRectangle rc, FillStroke fillStroke) override;
+ void RectangleFrame(PRectangle rc, Stroke stroke) override;
+ void FillRectangle(PRectangle rc, Fill fill) override;
+ void FillRectangleAligned(PRectangle rc, Fill fill) override;
+ void FillRectangle(PRectangle rc, Surface &surfacePattern) override;
+ void RoundedRectangle(PRectangle rc, FillStroke fillStroke) override;
+ void AlphaRectangle(PRectangle rc, XYPOSITION cornerSize, FillStroke fillStroke) override;
+ void GradientRectangle(PRectangle rc, const std::vector<ColourStop> &stops, GradientOptions options) override;
+ void DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) override;
+ void Ellipse(PRectangle rc, FillStroke fillStroke) override;
+ void Stadium(PRectangle rc, FillStroke fillStroke, Ends ends) override;
+ void Copy(PRectangle rc, Point from, Surface &surfaceSource) override;
+
+ std::unique_ptr<IScreenLineLayout> Layout(const IScreenLine *screenLine) override;
+
+ void DrawTextCommon(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, UINT fuOptions);
+ void DrawTextNoClip(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourRGBA fore, ColourRGBA back) override;
+ void DrawTextClipped(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourRGBA fore, ColourRGBA back) override;
+ void DrawTextTransparent(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourRGBA fore) override;
+ void MeasureWidths(const Font *font_, std::string_view text, XYPOSITION *positions) override;
+ XYPOSITION WidthText(const Font *font_, std::string_view text) override;
+
+ void DrawTextCommonUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, UINT fuOptions);
+ void DrawTextNoClipUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourRGBA fore, ColourRGBA back) override;
+ void DrawTextClippedUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourRGBA fore, ColourRGBA back) override;
+ void DrawTextTransparentUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourRGBA fore) override;
+ void MeasureWidthsUTF8(const Font *font_, std::string_view text, XYPOSITION *positions) override;
+ XYPOSITION WidthTextUTF8(const Font *font_, std::string_view text) override;
+
+ XYPOSITION Ascent(const Font *font_) override;
+ XYPOSITION Descent(const Font *font_) override;
+ XYPOSITION InternalLeading(const Font *font_) override;
+ XYPOSITION Height(const Font *font_) override;
+ XYPOSITION AverageCharWidth(const Font *font_) override;
+
+ void SetClip(PRectangle rc) override;
+ void PopClip() override;
+ void FlushCachedState() override;
+ void FlushDrawing() override;
+};
+
+SurfaceGDI::SurfaceGDI(HDC hdcCompatible, int width, int height, SurfaceMode mode_, int logPixelsY_) noexcept {
+ hdc = ::CreateCompatibleDC(hdcCompatible);
+ hdcOwned = true;
+ bitmap = ::CreateCompatibleBitmap(hdcCompatible, width, height);
+ bitmapOld = SelectBitmap(hdc, bitmap);
+ ::SetTextAlign(hdc, TA_BASELINE);
+ mode = mode_;
+ logPixelsY = logPixelsY_;
+}
+
+SurfaceGDI::~SurfaceGDI() noexcept {
+ Clear();
+}
+
+void SurfaceGDI::Clear() noexcept {
+ if (penOld) {
+ ::SelectObject(hdc, penOld);
+ ::DeleteObject(pen);
+ penOld = {};
+ }
+ pen = {};
+ if (brushOld) {
+ ::SelectObject(hdc, brushOld);
+ ::DeleteObject(brush);
+ brushOld = {};
+ }
+ brush = {};
+ if (fontOld) {
+ // Fonts are not deleted as they are owned by a Font object
+ ::SelectObject(hdc, fontOld);
+ fontOld = {};
+ }
+ if (bitmapOld) {
+ ::SelectObject(hdc, bitmapOld);
+ ::DeleteObject(bitmap);
+ bitmapOld = {};
+ }
+ bitmap = {};
+ if (hdcOwned) {
+ ::DeleteDC(hdc);
+ hdc = {};
+ hdcOwned = false;
+ }
+}
+
+void SurfaceGDI::Release() noexcept {
+ Clear();
+}
+
+int SurfaceGDI::SupportsFeature(Supports feature) noexcept {
+ for (const Supports f : SupportsGDI) {
+ if (f == feature)
+ return 1;
+ }
+ return 0;
+}
+
+bool SurfaceGDI::Initialised() {
+ return hdc;
+}
+
+void SurfaceGDI::Init(WindowID wid) {
+ Release();
+ hdc = ::CreateCompatibleDC({});
+ hdcOwned = true;
+ ::SetTextAlign(hdc, TA_BASELINE);
+ logPixelsY = DpiForWindow(wid);
+}
+
+void SurfaceGDI::Init(SurfaceID sid, WindowID wid) {
+ Release();
+ hdc = static_cast<HDC>(sid);
+ ::SetTextAlign(hdc, TA_BASELINE);
+ // Windows on screen are scaled but printers are not.
+ const bool printing = ::GetDeviceCaps(hdc, TECHNOLOGY) != DT_RASDISPLAY;
+ logPixelsY = printing ? ::GetDeviceCaps(hdc, LOGPIXELSY) : DpiForWindow(wid);
+}
+
+std::unique_ptr<Surface> SurfaceGDI::AllocatePixMap(int width, int height) {
+ return std::make_unique<SurfaceGDI>(hdc, width, height, mode, logPixelsY);
+}
+
+void SurfaceGDI::SetMode(SurfaceMode mode_) {
+ mode = mode_;
+}
+
+void SurfaceGDI::PenColour(ColourRGBA fore, XYPOSITION widthStroke) noexcept {
+ if (pen) {
+ ::SelectObject(hdc, penOld);
+ ::DeleteObject(pen);
+ pen = {};
+ penOld = {};
+ }
+ const DWORD penWidth = std::lround(widthStroke);
+ const COLORREF penColour = fore.OpaqueRGB();
+ if (widthStroke > 1) {
+ const LOGBRUSH brushParameters{ BS_SOLID, penColour, 0 };
+ pen = ::ExtCreatePen(PS_GEOMETRIC | PS_ENDCAP_ROUND | PS_JOIN_MITER,
+ penWidth,
+ &brushParameters,
+ 0,
+ nullptr);
+ } else {
+ pen = ::CreatePen(PS_INSIDEFRAME, penWidth, penColour);
+ }
+ penOld = SelectPen(hdc, pen);
+}
+
+void SurfaceGDI::BrushColour(ColourRGBA back) noexcept {
+ if (brush) {
+ ::SelectObject(hdc, brushOld);
+ ::DeleteObject(brush);
+ brush = {};
+ brushOld = {};
+ }
+ brush = ::CreateSolidBrush(back.OpaqueRGB());
+ brushOld = SelectBrush(hdc, brush);
+}
+
+void SurfaceGDI::SetFont(const Font *font_) {
+ const FontGDI *pfm = dynamic_cast<const FontGDI *>(font_);
+ PLATFORM_ASSERT(pfm);
+ if (!pfm) {
+ throw std::runtime_error("SurfaceGDI::SetFont: wrong Font type.");
+ }
+ if (fontOld) {
+ SelectFont(hdc, pfm->hfont);
+ } else {
+ fontOld = SelectFont(hdc, pfm->hfont);
+ }
+}
+
+int SurfaceGDI::LogPixelsY() {
+ return logPixelsY;
+}
+
+int SurfaceGDI::PixelDivisions() {
+ // Win32 uses device pixels.
+ return 1;
+}
+
+int SurfaceGDI::DeviceHeightFont(int points) {
+ return ::MulDiv(points, LogPixelsY(), 72);
+}
+
+void SurfaceGDI::LineDraw(Point start, Point end, Stroke stroke) {
+ PenColour(stroke.colour, stroke.width);
+ ::MoveToEx(hdc, std::lround(std::floor(start.x)), std::lround(std::floor(start.y)), nullptr);
+ ::LineTo(hdc, std::lround(std::floor(end.x)), std::lround(std::floor(end.y)));
+}
+
+void SurfaceGDI::PolyLine(const Point *pts, size_t npts, Stroke stroke) {
+ PLATFORM_ASSERT(npts > 1);
+ if (npts <= 1) {
+ return;
+ }
+ PenColour(stroke.colour, stroke.width);
+ std::vector<POINT> outline;
+ std::transform(pts, pts + npts, std::back_inserter(outline), POINTFromPoint);
+ ::Polyline(hdc, outline.data(), static_cast<int>(npts));
+}
+
+void SurfaceGDI::Polygon(const Point *pts, size_t npts, FillStroke fillStroke) {
+ PenColour(fillStroke.stroke.colour.WithoutAlpha(), fillStroke.stroke.width);
+ BrushColour(fillStroke.fill.colour.WithoutAlpha());
+ std::vector<POINT> outline;
+ std::transform(pts, pts + npts, std::back_inserter(outline), POINTFromPoint);
+ ::Polygon(hdc, outline.data(), static_cast<int>(npts));
+}
+
+void SurfaceGDI::RectangleDraw(PRectangle rc, FillStroke fillStroke) {
+ RectangleFrame(rc, fillStroke.stroke);
+ FillRectangle(rc.Inset(fillStroke.stroke.width), fillStroke.fill.colour);
+}
+
+void SurfaceGDI::RectangleFrame(PRectangle rc, Stroke stroke) {
+ BrushColour(stroke.colour);
+ const RECT rcw = RectFromPRectangle(rc);
+ ::FrameRect(hdc, &rcw, brush);
+}
+
+void SurfaceGDI::FillRectangle(PRectangle rc, Fill fill) {
+ if (fill.colour.IsOpaque()) {
+ // Using ExtTextOut rather than a FillRect ensures that no dithering occurs.
+ // There is no need to allocate a brush either.
+ const RECT rcw = RectFromPRectangle(rc);
+ ::SetBkColor(hdc, fill.colour.OpaqueRGB());
+ ::ExtTextOut(hdc, rcw.left, rcw.top, ETO_OPAQUE, &rcw, TEXT(""), 0, nullptr);
+ } else {
+ AlphaRectangle(rc, 0, FillStroke(fill.colour));
+ }
+}
+
+void SurfaceGDI::FillRectangleAligned(PRectangle rc, Fill fill) {
+ FillRectangle(PixelAlign(rc, 1), fill);
+}
+
+void SurfaceGDI::FillRectangle(PRectangle rc, Surface &surfacePattern) {
+ HBRUSH br{};
+ if (SurfaceGDI *psgdi = dynamic_cast<SurfaceGDI *>(&surfacePattern); psgdi && psgdi->bitmap) {
+ br = ::CreatePatternBrush(psgdi->bitmap);
+ } else { // Something is wrong so display in red
+ br = ::CreateSolidBrush(RGB(0xff, 0, 0));
+ }
+ const RECT rcw = RectFromPRectangle(rc);
+ ::FillRect(hdc, &rcw, br);
+ ::DeleteObject(br);
+}
+
+void SurfaceGDI::RoundedRectangle(PRectangle rc, FillStroke fillStroke) {
+ PenColour(fillStroke.stroke.colour, fillStroke.stroke.width);
+ BrushColour(fillStroke.fill.colour);
+ const RECT rcw = RectFromPRectangle(rc);
+ ::RoundRect(hdc,
+ rcw.left + 1, rcw.top,
+ rcw.right - 1, rcw.bottom,
+ 8, 8);
+}
+
+// DIBSection is bitmap with some drawing operations used by SurfaceGDI.
+class DIBSection {
+ GDIBitMap bm;
+ SIZE size{};
+ DWORD *pixels = nullptr;
+public:
+ DIBSection(HDC hdc, SIZE size_) noexcept;
+ explicit operator bool() const noexcept {
+ return bm && pixels;
+ }
+ [[nodiscard]] DWORD *Pixels() const noexcept {
+ return pixels;
+ }
+ [[nodiscard]] unsigned char *Bytes() const noexcept {
+ return reinterpret_cast<unsigned char *>(pixels);
+ }
+ [[nodiscard]] HDC DC() const noexcept {
+ return bm.DC();
+ }
+ void SetPixel(LONG x, LONG y, DWORD value) noexcept {
+ PLATFORM_ASSERT(x >= 0);
+ PLATFORM_ASSERT(y >= 0);
+ PLATFORM_ASSERT(x < size.cx);
+ PLATFORM_ASSERT(y < size.cy);
+ pixels[y * size.cx + x] = value;
+ }
+ void SetSymmetric(LONG x, LONG y, DWORD value) noexcept;
+};
+
+DIBSection::DIBSection(HDC hdc, SIZE size_) noexcept : size(size_) {
+ // -size.y makes bitmap start from top
+ bm.Create(hdc, size.cx, -size.cy, &pixels);
+}
+
+void DIBSection::SetSymmetric(LONG x, LONG y, DWORD value) noexcept {
+ // Plot a point symmetrically to all 4 quadrants
+ const LONG xSymmetric = size.cx - 1 - x;
+ const LONG ySymmetric = size.cy - 1 - y;
+ SetPixel(x, y, value);
+ SetPixel(xSymmetric, y, value);
+ SetPixel(x, ySymmetric, value);
+ SetPixel(xSymmetric, ySymmetric, value);
+}
+
+ColourRGBA GradientValue(const std::vector<ColourStop> &stops, XYPOSITION proportion) noexcept {
+ for (size_t stop = 0; stop < stops.size() - 1; stop++) {
+ // Loop through each pair of stops
+ const XYPOSITION positionStart = stops[stop].position;
+ const XYPOSITION positionEnd = stops[stop + 1].position;
+ if ((proportion >= positionStart) && (proportion <= positionEnd)) {
+ const XYPOSITION proportionInPair = (proportion - positionStart) /
+ (positionEnd - positionStart);
+ return stops[stop].colour.MixedWith(stops[stop + 1].colour, proportionInPair);
+ }
+ }
+ // Loop should always find a value
+ return ColourRGBA();
+}
+
+constexpr DWORD dwordFromBGRA(byte b, byte g, byte r, byte a) noexcept {
+ return (a << 24) | (r << 16) | (g << 8) | b;
+}
+
+constexpr byte AlphaScaled(unsigned char component, unsigned int alpha) noexcept {
+ return static_cast<byte>(component * alpha / 255);
+}
+
+constexpr DWORD dwordMultiplied(ColourRGBA colour) noexcept {
+ return dwordFromBGRA(
+ AlphaScaled(colour.GetBlue(), colour.GetAlpha()),
+ AlphaScaled(colour.GetGreen(), colour.GetAlpha()),
+ AlphaScaled(colour.GetRed(), colour.GetAlpha()),
+ colour.GetAlpha());
+}
+
+constexpr BLENDFUNCTION mergeAlpha = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA };
+
+void SurfaceGDI::AlphaRectangle(PRectangle rc, XYPOSITION cornerSize, FillStroke fillStroke) {
+ // TODO: Implement strokeWidth
+ const RECT rcw = RectFromPRectangle(rc);
+ const SIZE size = SizeOfRect(rcw);
+
+ if (size.cx > 0) {
+
+ DIBSection section(hdc, size);
+
+ if (section) {
+
+ // Ensure not distorted too much by corners when small
+ const LONG corner = std::min(static_cast<LONG>(cornerSize), (std::min(size.cx, size.cy) / 2) - 2);
+
+ constexpr DWORD valEmpty = dwordFromBGRA(0, 0, 0, 0);
+ const DWORD valFill = dwordMultiplied(fillStroke.fill.colour);
+ const DWORD valOutline = dwordMultiplied(fillStroke.stroke.colour);
+
+ // Draw a framed rectangle
+ for (int y = 0; y < size.cy; y++) {
+ for (int x = 0; x < size.cx; x++) {
+ if ((x == 0) || (x == size.cx - 1) || (y == 0) || (y == size.cy - 1)) {
+ section.SetPixel(x, y, valOutline);
+ } else {
+ section.SetPixel(x, y, valFill);
+ }
+ }
+ }
+
+ // Make the corners transparent
+ for (LONG c = 0; c < corner; c++) {
+ for (LONG x = 0; x < c + 1; x++) {
+ section.SetSymmetric(x, c - x, valEmpty);
+ }
+ }
+
+ // Draw the corner frame pieces
+ for (LONG x = 1; x < corner; x++) {
+ section.SetSymmetric(x, corner - x, valOutline);
+ }
+
+ GdiAlphaBlend(hdc, rcw.left, rcw.top, size.cx, size.cy, section.DC(), 0, 0, size.cx, size.cy, mergeAlpha);
+ }
+ } else {
+ BrushColour(fillStroke.stroke.colour);
+ FrameRect(hdc, &rcw, brush);
+ }
+}
+
+void SurfaceGDI::GradientRectangle(PRectangle rc, const std::vector<ColourStop> &stops, GradientOptions options) {
+
+ const RECT rcw = RectFromPRectangle(rc);
+ const SIZE size = SizeOfRect(rcw);
+
+ DIBSection section(hdc, size);
+
+ if (section) {
+
+ if (options == GradientOptions::topToBottom) {
+ for (LONG y = 0; y < size.cy; y++) {
+ // Find y/height proportional colour
+ const XYPOSITION proportion = y / (rc.Height() - 1.0f);
+ const ColourRGBA mixed = GradientValue(stops, proportion);
+ const DWORD valFill = dwordMultiplied(mixed);
+ for (LONG x = 0; x < size.cx; x++) {
+ section.SetPixel(x, y, valFill);
+ }
+ }
+ } else {
+ for (LONG x = 0; x < size.cx; x++) {
+ // Find x/width proportional colour
+ const XYPOSITION proportion = x / (rc.Width() - 1.0f);
+ const ColourRGBA mixed = GradientValue(stops, proportion);
+ const DWORD valFill = dwordMultiplied(mixed);
+ for (LONG y = 0; y < size.cy; y++) {
+ section.SetPixel(x, y, valFill);
+ }
+ }
+ }
+
+ GdiAlphaBlend(hdc, rcw.left, rcw.top, size.cx, size.cy, section.DC(), 0, 0, size.cx, size.cy, mergeAlpha);
+ }
+}
+
+void SurfaceGDI::DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) {
+ if (rc.Width() > 0) {
+ if (rc.Width() > width)
+ rc.left += std::floor((rc.Width() - width) / 2);
+ rc.right = rc.left + width;
+ if (rc.Height() > height)
+ rc.top += std::floor((rc.Height() - height) / 2);
+ rc.bottom = rc.top + height;
+
+ const SIZE size{ width, height };
+ DIBSection section(hdc, size);
+ if (section) {
+ RGBAImage::BGRAFromRGBA(section.Bytes(), pixelsImage, static_cast<size_t>(width) * height);
+ GdiAlphaBlend(hdc, static_cast<int>(rc.left), static_cast<int>(rc.top),
+ static_cast<int>(rc.Width()), static_cast<int>(rc.Height()), section.DC(),
+ 0, 0, width, height, mergeAlpha);
+ }
+ }
+}
+
+void SurfaceGDI::Ellipse(PRectangle rc, FillStroke fillStroke) {
+ PenColour(fillStroke.stroke.colour, fillStroke.stroke.width);
+ BrushColour(fillStroke.fill.colour);
+ const RECT rcw = RectFromPRectangle(rc);
+ ::Ellipse(hdc, rcw.left, rcw.top, rcw.right, rcw.bottom);
+}
+
+void SurfaceGDI::Stadium(PRectangle rc, FillStroke fillStroke, [[maybe_unused]] Ends ends) {
+ // TODO: Implement properly - the rectangle is just a placeholder
+ RectangleDraw(rc, fillStroke);
+}
+
+void SurfaceGDI::Copy(PRectangle rc, Point from, Surface &surfaceSource) {
+ ::BitBlt(hdc,
+ static_cast<int>(rc.left), static_cast<int>(rc.top),
+ static_cast<int>(rc.Width()), static_cast<int>(rc.Height()),
+ dynamic_cast<SurfaceGDI &>(surfaceSource).hdc,
+ static_cast<int>(from.x), static_cast<int>(from.y), SRCCOPY);
+}
+
+std::unique_ptr<IScreenLineLayout> SurfaceGDI::Layout(const IScreenLine *) {
+ return {};
+}
+
+using TextPositionsI = VarBuffer<int, stackBufferLength>;
+
+void SurfaceGDI::DrawTextCommon(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, UINT fuOptions) {
+ SetFont(font_);
+ const RECT rcw = RectFromPRectangle(rc);
+ const int x = static_cast<int>(rc.left);
+ const int yBaseInt = static_cast<int>(ybase);
+
+ if (mode.codePage == CpUtf8) {
+ const TextWide tbuf(text, mode.codePage);
+ ::ExtTextOutW(hdc, x, yBaseInt, fuOptions, &rcw, tbuf.buffer, tbuf.tlen, nullptr);
+ } else {
+ ::ExtTextOutA(hdc, x, yBaseInt, fuOptions, &rcw, text.data(), static_cast<UINT>(text.length()), nullptr);
+ }
+}
+
+void SurfaceGDI::DrawTextNoClip(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text,
+ ColourRGBA fore, ColourRGBA back) {
+ ::SetTextColor(hdc, fore.OpaqueRGB());
+ ::SetBkColor(hdc, back.OpaqueRGB());
+ DrawTextCommon(rc, font_, ybase, text, ETO_OPAQUE);
+}
+
+void SurfaceGDI::DrawTextClipped(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text,
+ ColourRGBA fore, ColourRGBA back) {
+ ::SetTextColor(hdc, fore.OpaqueRGB());
+ ::SetBkColor(hdc, back.OpaqueRGB());
+ DrawTextCommon(rc, font_, ybase, text, ETO_OPAQUE | ETO_CLIPPED);
+}
+
+void SurfaceGDI::DrawTextTransparent(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text,
+ ColourRGBA fore) {
+ // Avoid drawing spaces in transparent mode
+ for (const char ch : text) {
+ if (ch != ' ') {
+ ::SetTextColor(hdc, fore.OpaqueRGB());
+ ::SetBkMode(hdc, TRANSPARENT);
+ DrawTextCommon(rc, font_, ybase, text, 0);
+ ::SetBkMode(hdc, OPAQUE);
+ return;
+ }
+ }
+}
+
+void SurfaceGDI::MeasureWidths(const Font *font_, std::string_view text, XYPOSITION *positions) {
+ // Zero positions to avoid random behaviour on failure.
+ std::fill(positions, positions + text.length(), 0.0f);
+ SetFont(font_);
+ SIZE sz = { 0,0 };
+ int fit = 0;
+ int i = 0;
+ const int len = static_cast<int>(text.length());
+ if (mode.codePage == CpUtf8) {
+ const TextWide tbuf(text, mode.codePage);
+ TextPositionsI poses(tbuf.tlen);
+ if (!::GetTextExtentExPointW(hdc, tbuf.buffer, tbuf.tlen, maxWidthMeasure, &fit, poses.buffer, &sz)) {
+ // Failure
+ return;
+ }
+ // Map the widths given for UTF-16 characters back onto the UTF-8 input string
+ for (int ui = 0; ui < fit; ui++) {
+ const unsigned char uch = text[i];
+ const unsigned int byteCount = UTF8BytesOfLead[uch];
+ if (byteCount == 4) { // Non-BMP
+ ui++;
+ }
+ for (unsigned int bytePos = 0; (bytePos < byteCount) && (i < len); bytePos++) {
+ positions[i++] = static_cast<XYPOSITION>(poses.buffer[ui]);
+ }
+ }
+ } else {
+ TextPositionsI poses(len);
+ if (!::GetTextExtentExPointA(hdc, text.data(), len, maxWidthMeasure, &fit, poses.buffer, &sz)) {
+ // Eeek - a NULL DC or other foolishness could cause this.
+ return;
+ }
+ while (i < fit) {
+ positions[i] = static_cast<XYPOSITION>(poses.buffer[i]);
+ i++;
+ }
+ }
+ // If any positions not filled in then use the last position for them
+ const XYPOSITION lastPos = (fit > 0) ? positions[fit - 1] : 0.0f;
+ std::fill(positions + i, positions + text.length(), lastPos);
+}
+
+XYPOSITION SurfaceGDI::WidthText(const Font *font_, std::string_view text) {
+ SetFont(font_);
+ SIZE sz = { 0,0 };
+ if (!(mode.codePage == CpUtf8)) {
+ ::GetTextExtentPoint32A(hdc, text.data(), std::min(static_cast<int>(text.length()), maxLenText), &sz);
+ } else {
+ const TextWide tbuf(text, mode.codePage);
+ ::GetTextExtentPoint32W(hdc, tbuf.buffer, tbuf.tlen, &sz);
+ }
+ return static_cast<XYPOSITION>(sz.cx);
+}
+
+void SurfaceGDI::DrawTextCommonUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, UINT fuOptions) {
+ SetFont(font_);
+ const RECT rcw = RectFromPRectangle(rc);
+ const int x = static_cast<int>(rc.left);
+ const int yBaseInt = static_cast<int>(ybase);
+
+ const TextWide tbuf(text, CpUtf8);
+ ::ExtTextOutW(hdc, x, yBaseInt, fuOptions, &rcw, tbuf.buffer, tbuf.tlen, nullptr);
+}
+
+void SurfaceGDI::DrawTextNoClipUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text,
+ ColourRGBA fore, ColourRGBA back) {
+ ::SetTextColor(hdc, fore.OpaqueRGB());
+ ::SetBkColor(hdc, back.OpaqueRGB());
+ DrawTextCommonUTF8(rc, font_, ybase, text, ETO_OPAQUE);
+}
+
+void SurfaceGDI::DrawTextClippedUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text,
+ ColourRGBA fore, ColourRGBA back) {
+ ::SetTextColor(hdc, fore.OpaqueRGB());
+ ::SetBkColor(hdc, back.OpaqueRGB());
+ DrawTextCommonUTF8(rc, font_, ybase, text, ETO_OPAQUE | ETO_CLIPPED);
+}
+
+void SurfaceGDI::DrawTextTransparentUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text,
+ ColourRGBA fore) {
+ // Avoid drawing spaces in transparent mode
+ for (const char ch : text) {
+ if (ch != ' ') {
+ ::SetTextColor(hdc, fore.OpaqueRGB());
+ ::SetBkMode(hdc, TRANSPARENT);
+ DrawTextCommonUTF8(rc, font_, ybase, text, 0);
+ ::SetBkMode(hdc, OPAQUE);
+ return;
+ }
+ }
+}
+
+void SurfaceGDI::MeasureWidthsUTF8(const Font *font_, std::string_view text, XYPOSITION *positions) {
+ // Zero positions to avoid random behaviour on failure.
+ std::fill(positions, positions + text.length(), 0.0f);
+ SetFont(font_);
+ SIZE sz = { 0,0 };
+ int fit = 0;
+ int i = 0;
+ const int len = static_cast<int>(text.length());
+ const TextWide tbuf(text, CpUtf8);
+ TextPositionsI poses(tbuf.tlen);
+ if (!::GetTextExtentExPointW(hdc, tbuf.buffer, tbuf.tlen, maxWidthMeasure, &fit, poses.buffer, &sz)) {
+ // Failure
+ return;
+ }
+ // Map the widths given for UTF-16 characters back onto the UTF-8 input string
+ for (int ui = 0; ui < fit; ui++) {
+ const unsigned char uch = text[i];
+ const unsigned int byteCount = UTF8BytesOfLead[uch];
+ if (byteCount == 4) { // Non-BMP
+ ui++;
+ }
+ for (unsigned int bytePos = 0; (bytePos < byteCount) && (i < len); bytePos++) {
+ positions[i++] = static_cast<XYPOSITION>(poses.buffer[ui]);
+ }
+ }
+ // If any positions not filled in then use the last position for them
+ const XYPOSITION lastPos = (fit > 0) ? positions[fit - 1] : 0.0f;
+ std::fill(positions + i, positions + text.length(), lastPos);
+}
+
+XYPOSITION SurfaceGDI::WidthTextUTF8(const Font *font_, std::string_view text) {
+ SetFont(font_);
+ SIZE sz = { 0,0 };
+ const TextWide tbuf(text, CpUtf8);
+ ::GetTextExtentPoint32W(hdc, tbuf.buffer, tbuf.tlen, &sz);
+ return static_cast<XYPOSITION>(sz.cx);
+}
+
+XYPOSITION SurfaceGDI::Ascent(const Font *font_) {
+ SetFont(font_);
+ TEXTMETRIC tm;
+ ::GetTextMetrics(hdc, &tm);
+ return static_cast<XYPOSITION>(tm.tmAscent);
+}
+
+XYPOSITION SurfaceGDI::Descent(const Font *font_) {
+ SetFont(font_);
+ TEXTMETRIC tm;
+ ::GetTextMetrics(hdc, &tm);
+ return static_cast<XYPOSITION>(tm.tmDescent);
+}
+
+XYPOSITION SurfaceGDI::InternalLeading(const Font *font_) {
+ SetFont(font_);
+ TEXTMETRIC tm;
+ ::GetTextMetrics(hdc, &tm);
+ return static_cast<XYPOSITION>(tm.tmInternalLeading);
+}
+
+XYPOSITION SurfaceGDI::Height(const Font *font_) {
+ SetFont(font_);
+ TEXTMETRIC tm;
+ ::GetTextMetrics(hdc, &tm);
+ return static_cast<XYPOSITION>(tm.tmHeight);
+}
+
+XYPOSITION SurfaceGDI::AverageCharWidth(const Font *font_) {
+ SetFont(font_);
+ TEXTMETRIC tm;
+ ::GetTextMetrics(hdc, &tm);
+ return static_cast<XYPOSITION>(tm.tmAveCharWidth);
+}
+
+void SurfaceGDI::SetClip(PRectangle rc) {
+ ::SaveDC(hdc);
+ ::IntersectClipRect(hdc, static_cast<int>(rc.left), static_cast<int>(rc.top),
+ static_cast<int>(rc.right), static_cast<int>(rc.bottom));
+}
+
+void SurfaceGDI::PopClip() {
+ ::RestoreDC(hdc, -1);
+}
+
+void SurfaceGDI::FlushCachedState() {
+ pen = {};
+ brush = {};
+}
+
+void SurfaceGDI::FlushDrawing() {
+}
+
+}
+
+namespace Scintilla::Internal {
+
+std::shared_ptr<Font> FontGDI_Allocate(const FontParameters &fp) {
+ return std::make_shared<FontGDI>(fp);
+}
+
+std::unique_ptr<Surface> SurfaceGDI_Allocate() {
+ return std::make_unique<SurfaceGDI>();
+}
+
+}