// Scintilla source code edit control /** @file SurfaceGDI.cxx ** Implementation of drawing to GDI on Windows. **/ // Copyright 1998-2003 by Neil Hodgson // The License.txt file describes the conditions under which this software may be distributed. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // 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 #include #include #include #include #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(weight); lf.lfItalic = italic ? 1 : 0; lf.lfCharSet = static_cast(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 Duplicate() const override { HFONT hfontCopy = HFont(); return std::make_unique(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 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 &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 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(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 SurfaceGDI::AllocatePixMap(int width, int height) { return std::make_unique(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(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(), pointsPerInch); } 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 outline; std::transform(pts, pts + npts, std::back_inserter(outline), POINTFromPoint); ::Polyline(hdc, outline.data(), static_cast(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 outline; std::transform(pts, pts + npts, std::back_inserter(outline), POINTFromPoint); ::Polygon(hdc, outline.data(), static_cast(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(&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); constexpr int cornerSize = 8; ::RoundRect(hdc, rcw.left + 1, rcw.top, rcw.right - 1, rcw.bottom, cornerSize, cornerSize); } // 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(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 &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 { constexpr int aShift = 24; constexpr int rShift = 16; constexpr int gShift = 8; return (a << aShift) | (r << rShift) | (g << gShift) | b; } constexpr byte AlphaScaled(unsigned char component, unsigned int alpha) noexcept { constexpr byte maxByte = 0xFFU; return (component * alpha / maxByte) & maxByte; } 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(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 &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(width) * height); GdiAlphaBlend(hdc, static_cast(rc.left), static_cast(rc.top), static_cast(rc.Width()), static_cast(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(rc.left), static_cast(rc.top), static_cast(rc.Width()), static_cast(rc.Height()), dynamic_cast(surfaceSource).hdc, static_cast(from.x), static_cast(from.y), SRCCOPY); } std::unique_ptr SurfaceGDI::Layout(const IScreenLine *) { return {}; } using TextPositionsI = VarBuffer; 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(rc.left); const int yBaseInt = static_cast(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(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(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(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(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(text.length()), maxLenText), &sz); } else { const TextWide tbuf(text, mode.codePage); ::GetTextExtentPoint32W(hdc, tbuf.buffer, tbuf.tlen, &sz); } return static_cast(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(rc.left); const int yBaseInt = static_cast(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(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(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(sz.cx); } XYPOSITION SurfaceGDI::Ascent(const Font *font_) { SetFont(font_); TEXTMETRIC tm; ::GetTextMetrics(hdc, &tm); return static_cast(tm.tmAscent); } XYPOSITION SurfaceGDI::Descent(const Font *font_) { SetFont(font_); TEXTMETRIC tm; ::GetTextMetrics(hdc, &tm); return static_cast(tm.tmDescent); } XYPOSITION SurfaceGDI::InternalLeading(const Font *font_) { SetFont(font_); TEXTMETRIC tm; ::GetTextMetrics(hdc, &tm); return static_cast(tm.tmInternalLeading); } XYPOSITION SurfaceGDI::Height(const Font *font_) { SetFont(font_); TEXTMETRIC tm; ::GetTextMetrics(hdc, &tm); return static_cast(tm.tmHeight); } XYPOSITION SurfaceGDI::AverageCharWidth(const Font *font_) { SetFont(font_); TEXTMETRIC tm; ::GetTextMetrics(hdc, &tm); return static_cast(tm.tmAveCharWidth); } void SurfaceGDI::SetClip(PRectangle rc) { ::SaveDC(hdc); ::IntersectClipRect(hdc, static_cast(rc.left), static_cast(rc.top), static_cast(rc.right), static_cast(rc.bottom)); } void SurfaceGDI::PopClip() { ::RestoreDC(hdc, -1); } void SurfaceGDI::FlushCachedState() { pen = {}; brush = {}; } void SurfaceGDI::FlushDrawing() { } } namespace Scintilla::Internal { std::shared_ptr FontGDI_Allocate(const FontParameters &fp) { return std::make_shared(fp); } std::unique_ptr SurfaceGDI_Allocate() { return std::make_unique(); } }