diff options
Diffstat (limited to 'win32')
-rw-r--r-- | win32/ListBox.cxx | 989 | ||||
-rw-r--r-- | win32/ListBox.h | 18 | ||||
-rw-r--r-- | win32/PlatWin.cxx | 3517 | ||||
-rw-r--r-- | win32/PlatWin.h | 102 | ||||
-rw-r--r-- | win32/Scintilla.vcxproj | 3 | ||||
-rw-r--r-- | win32/ScintillaWin.cxx | 3 | ||||
-rw-r--r-- | win32/SurfaceD2D.cxx | 1738 | ||||
-rw-r--r-- | win32/SurfaceD2D.h | 50 | ||||
-rw-r--r-- | win32/SurfaceGDI.cxx | 884 | ||||
-rw-r--r-- | win32/SurfaceGDI.h | 18 | ||||
-rw-r--r-- | win32/WinTypes.h | 7 | ||||
-rw-r--r-- | win32/deps.mak | 43 | ||||
-rw-r--r-- | win32/makefile | 3 | ||||
-rw-r--r-- | win32/nmdeps.mak | 43 | ||||
-rw-r--r-- | win32/scintilla.mak | 3 |
15 files changed, 3895 insertions, 3526 deletions
diff --git a/win32/ListBox.cxx b/win32/ListBox.cxx new file mode 100644 index 000000000..88b44d9ed --- /dev/null +++ b/win32/ListBox.cxx @@ -0,0 +1,989 @@ +// Scintilla source code edit control +/** @file ListBox.cxx + ** Implementation of list box 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 <wrl.h> +using Microsoft::WRL::ComPtr; + +#if !defined(DISABLE_D2D) +#define USE_D2D 1 +#endif + +#if defined(USE_D2D) +#include <d2d1_1.h> +#include <d3d11_1.h> +#include <dwrite_1.h> +#endif + +#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 "ListBox.h" +#if defined(USE_D2D) +#include "SurfaceD2D.h" +#endif + +using namespace Scintilla; +using namespace Scintilla::Internal; + +namespace { + +struct ListItemData { + const char *text; + int pixId; +}; + +class LineToItem { + std::vector<char> words; + + std::vector<ListItemData> data; + +public: + void Clear() noexcept { + words.clear(); + data.clear(); + } + + [[nodiscard]] ListItemData Get(size_t index) const noexcept { + if (index < data.size()) { + return data[index]; + } + ListItemData missing = {"", -1}; + return missing; + } + [[nodiscard]] int Count() const noexcept { + return static_cast<int>(data.size()); + } + + void AllocItem(const char *text, int pixId) { + const ListItemData lid = { text, pixId }; + data.push_back(lid); + } + + char *SetWords(const char *s) { + words = std::vector<char>(s, s+strlen(s)+1); + return words.data(); + } +}; + +const TCHAR ListBoxX_ClassName[] = TEXT("ListBoxX"); + +ColourRGBA ColourElement(std::optional<ColourRGBA> colour, int nIndex) { + if (colour.has_value()) { + return colour.value(); + } + const DWORD colourValue = ::GetSysColor(nIndex); + return ColourRGBA(colourValue); +} + +struct LBGraphics { + GDIBitMap bm; + std::unique_ptr<Surface> pixmapLine; +#if defined(USE_D2D) + DCRenderTarget pBMDCTarget; +#endif + + void Release() noexcept { + pixmapLine.reset(); +#if defined(USE_D2D) + pBMDCTarget = nullptr; +#endif + bm.Release(); + } +}; + +} + +class ListBoxX : public ListBox { + int lineHeight = 10; + HFONT fontCopy {}; + std::unique_ptr<FontWin> fontWin; + Technology technology = Technology::Default; + RGBAImageSet images; + LineToItem lti; + HWND lb {}; + bool unicodeMode = false; + int codePage = 0; + int desiredVisibleRows = 9; + int maxItemCharacters = 0; + unsigned int aveCharWidth = 8; + Window *parent = nullptr; + int ctrlID = 0; + UINT dpi = USER_DEFAULT_SCREEN_DPI; + IListBoxDelegate *delegate = nullptr; + unsigned int maxCharWidth = 1; + WPARAM resizeHit = 0; + PRectangle rcPreSize; + Point dragOffset; + Point location; // Caret location at which the list is opened + MouseWheelDelta wheelDelta; + ListOptions options; + DWORD frameStyle = WS_THICKFRAME; + + LBGraphics graphics; + + HWND GetHWND() const noexcept; + void AppendListItem(const char *text, const char *numword); + void AdjustWindowRect(PRectangle *rc, UINT dpiAdjust) const noexcept; + int ItemHeight() const noexcept; + int MinClientWidth() const noexcept; + int TextOffset() const noexcept; + POINT GetClientExtent() const noexcept; + POINT MinTrackSize() const noexcept; + POINT MaxTrackSize() const noexcept; + void SetRedraw(bool on) noexcept; + void OnDoubleClick(); + void OnSelChange(); + void ResizeToCursor(); + void StartResize(WPARAM); + LRESULT NcHitTest(WPARAM, LPARAM) const; + void CentreItem(int n); + void AllocateBitMap(); + static LRESULT PASCAL ControlWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam); + + static constexpr POINT ItemInset {0, 0}; // Padding around whole item + static constexpr POINT TextInset {2, 0}; // Padding around text + static constexpr POINT ImageInset {1, 0}; // Padding around image + +public: + ListBoxX() = default; + ListBoxX(const ListBoxX &) = delete; + ListBoxX(ListBoxX &&) = delete; + ListBoxX &operator=(const ListBoxX &) = delete; + ListBoxX &operator=(ListBoxX &&) = delete; + ~ListBoxX() noexcept override { + if (fontCopy) { + ::DeleteObject(fontCopy); + fontCopy = {}; + } + graphics.Release(); + } + void SetFont(const Font *font) override; + void Create(Window &parent_, int ctrlID_, Point location_, int lineHeight_, bool unicodeMode_, Technology technology_) override; + void SetAverageCharWidth(int width) override; + void SetVisibleRows(int rows) override; + int GetVisibleRows() const override; + PRectangle GetDesiredRect() override; + int CaretFromEdge() override; + void Clear() noexcept override; + void Append(char *s, int type) override; + int Length() override; + void Select(int n) override; + int GetSelection() override; + int Find(const char *prefix) override; + std::string GetValue(int n) override; + void RegisterImage(int type, const char *xpm_data) override; + void RegisterRGBAImage(int type, int width, int height, const unsigned char *pixelsImage) override; + void ClearRegisteredImages() override; + void SetDelegate(IListBoxDelegate *lbDelegate) override; + void SetList(const char *list, char separator, char typesep) override; + void SetOptions(ListOptions options_) override; + void Draw(DRAWITEMSTRUCT *pDrawItem); + LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam); + static LRESULT PASCAL StaticWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam); +}; + +std::unique_ptr<ListBox> ListBox::Allocate() { + return std::make_unique<ListBoxX>(); +} + +void ListBoxX::Create(Window &parent_, int ctrlID_, Point location_, int lineHeight_, bool unicodeMode_, Technology technology_) { + parent = &parent_; + ctrlID = ctrlID_; + location = location_; + lineHeight = lineHeight_; + unicodeMode = unicodeMode_; + codePage = unicodeMode ? CpUtf8 : 0; + technology = technology_; + HWND hwndParent = HwndFromWindow(*parent); + HINSTANCE hinstanceParent = GetWindowInstance(hwndParent); + // Window created as popup so not clipped within parent client area + wid = ::CreateWindowEx( + WS_EX_WINDOWEDGE, ListBoxX_ClassName, TEXT(""), + WS_POPUP | frameStyle, + 100,100, 150,80, hwndParent, + {}, + hinstanceParent, + this); + + dpi = DpiForWindow(hwndParent); + POINT locationw = POINTFromPoint(location); + ::MapWindowPoints(hwndParent, {}, &locationw, 1); + location = PointFromPOINT(locationw); +} + +void ListBoxX::SetFont(const Font *font) { + const FontWin *pfm = dynamic_cast<const FontWin *>(font); + if (pfm) { + if (fontCopy) { + ::DeleteObject(fontCopy); + fontCopy = {}; + } + fontCopy = pfm->HFont(); + SetWindowFont(lb, fontCopy, 0); + fontWin = pfm->Duplicate(); + codePage = unicodeMode ? CpUtf8 : CodePageFromCharSet(fontWin->GetCharacterSet(), 1252); + graphics.Release(); + } +} + +void ListBoxX::SetAverageCharWidth(int width) { + aveCharWidth = width; +} + +void ListBoxX::SetVisibleRows(int rows) { + desiredVisibleRows = rows; +} + +int ListBoxX::GetVisibleRows() const { + return desiredVisibleRows; +} + +HWND ListBoxX::GetHWND() const noexcept { + return HwndFromWindowID(GetID()); +} + +PRectangle ListBoxX::GetDesiredRect() { + PRectangle rcDesired = GetPosition(); + + int rows = Length(); + if ((rows == 0) || (rows > desiredVisibleRows)) + rows = desiredVisibleRows; + rcDesired.bottom = rcDesired.top + ItemHeight() * rows; + + int width = MinClientWidth(); + int textSize = 0; + int averageCharWidth = 8; + + // Make a measuring surface + std::unique_ptr<Surface> surfaceItem(Surface::Allocate(technology)); + surfaceItem->Init(GetID()); + surfaceItem->SetMode(SurfaceMode(codePage, false)); + + // Find the widest item in pixels + const int items = lti.Count(); + for (int i = 0; i < items; i++) { + const ListItemData item = lti.Get(i); + const int itemTextSize = static_cast<int>(std::ceil( + surfaceItem->WidthText(fontWin.get(), item.text))); + textSize = std::max(textSize, itemTextSize); + } + + maxCharWidth = static_cast<int>(std::ceil(surfaceItem->WidthText(fontWin.get(), "W"))); + averageCharWidth = static_cast<int>(surfaceItem->AverageCharWidth(fontWin.get())); + + width = std::max({ width, textSize, (maxItemCharacters + 1) * averageCharWidth }); + + rcDesired.right = rcDesired.left + TextOffset() + width + (TextInset.x * 2); + if (Length() > rows) + rcDesired.right += SystemMetricsForDpi(SM_CXVSCROLL, dpi); + + AdjustWindowRect(&rcDesired, dpi); + return rcDesired; +} + +int ListBoxX::TextOffset() const noexcept { + const int pixWidth = images.GetWidth(); + return pixWidth == 0 ? ItemInset.x : ItemInset.x + pixWidth + (ImageInset.x * 2); +} + +int ListBoxX::CaretFromEdge() { + PRectangle rc; + AdjustWindowRect(&rc, dpi); + return TextOffset() + static_cast<int>(TextInset.x + (0 - rc.left) - 1); +} + +void ListBoxX::Clear() noexcept { + ListBox_ResetContent(lb); + maxItemCharacters = 0; + lti.Clear(); +} + +void ListBoxX::Append(char *, int) { + // This method is no longer called in Scintilla + PLATFORM_ASSERT(false); +} + +int ListBoxX::Length() { + return lti.Count(); +} + +void ListBoxX::Select(int n) { + // We are going to scroll to centre on the new selection and then select it, so disable + // redraw to avoid flicker caused by a painting new selection twice in unselected and then + // selected states + SetRedraw(false); + CentreItem(n); + ListBox_SetCurSel(lb, n); + OnSelChange(); + SetRedraw(true); +} + +int ListBoxX::GetSelection() { + return ListBox_GetCurSel(lb); +} + +// This is not actually called at present +int ListBoxX::Find(const char *) { + return LB_ERR; +} + +std::string ListBoxX::GetValue(int n) { + const ListItemData item = lti.Get(n); + return item.text; +} + +void ListBoxX::RegisterImage(int type, const char *xpm_data) { + XPM xpmImage(xpm_data); + images.AddImage(type, std::make_unique<RGBAImage>(xpmImage)); +} + +void ListBoxX::RegisterRGBAImage(int type, int width, int height, const unsigned char *pixelsImage) { + images.AddImage(type, std::make_unique<RGBAImage>(width, height, 1.0f, pixelsImage)); +} + +void ListBoxX::ClearRegisteredImages() { + images.Clear(); +} + +void ListBoxX::Draw(DRAWITEMSTRUCT *pDrawItem) { + if ((pDrawItem->itemAction != ODA_SELECT) && (pDrawItem->itemAction != ODA_DRAWENTIRE)) { + return; + } + if (!graphics.pixmapLine) { + AllocateBitMap(); + if (!graphics.pixmapLine) { + // Failed to allocate, so release fully and give up + graphics.Release(); + return; + } + } +#if defined(USE_D2D) + if (graphics.pBMDCTarget) { + graphics.pBMDCTarget->BeginDraw(); + } +#endif + + const PRectangle rcItemBase = PRectangleFromRECT(pDrawItem->rcItem); + const PRectangle rcItem(0, 0, rcItemBase.Width(), rcItemBase.Height()); + PRectangle rcBox = rcItem; + rcBox.left += TextOffset(); + ColourRGBA colourFore; + ColourRGBA colourBack; + if (pDrawItem->itemState & ODS_SELECTED) { + PRectangle rcImage = rcItem; + rcImage.right = rcBox.left; + // The image is not highlighted + graphics.pixmapLine->FillRectangle(rcImage, ColourElement(options.back, COLOR_WINDOW)); + colourBack = ColourElement(options.backSelected, COLOR_HIGHLIGHT); + graphics.pixmapLine->FillRectangle(rcBox, colourBack); + colourFore = ColourElement(options.foreSelected, COLOR_HIGHLIGHTTEXT); + } else { + colourBack = ColourElement(options.back, COLOR_WINDOW); + graphics.pixmapLine->FillRectangle(rcItem, colourBack); + colourFore = ColourElement(options.fore, COLOR_WINDOWTEXT); + } + + const ListItemData item = lti.Get(pDrawItem->itemID); + const int pixId = item.pixId; + const char *text = item.text; + + const PRectangle rcText = rcBox.Inset(Point(TextInset.x, TextInset.y)); + + const double ascent = graphics.pixmapLine->Ascent(fontWin.get()); + graphics.pixmapLine->DrawTextClipped(rcText, fontWin.get(), rcText.top + ascent, text, colourFore, colourBack); + + // Draw the image, if any + const RGBAImage *pimage = images.Get(pixId); + if (pimage) { + const XYPOSITION left = rcItem.left + ItemInset.x + ImageInset.x; + PRectangle rcImage = rcItem; + rcImage.left = left; + rcImage.right = rcImage.left + images.GetWidth(); + graphics.pixmapLine->DrawRGBAImage(rcImage, + pimage->GetWidth(), pimage->GetHeight(), pimage->Pixels()); + } + +#if defined(USE_D2D) + if (graphics.pBMDCTarget) { + const HRESULT hrEnd = graphics.pBMDCTarget->EndDraw(); + if (FAILED(hrEnd)) { + return; + } + } +#endif + + // Blit from hMemDC to hDC + const SIZE extent = SizeOfRect(pDrawItem->rcItem); + ::BitBlt(pDrawItem->hDC, pDrawItem->rcItem.left, pDrawItem->rcItem.top, extent.cx, extent.cy, graphics.bm.DC(), 0, 0, SRCCOPY); +} + +void ListBoxX::AppendListItem(const char *text, const char *numword) { + int pixId = -1; + if (numword) { + pixId = 0; + char ch; + while ((ch = *++numword) != '\0') { + pixId = 10 * pixId + (ch - '0'); + } + } + + lti.AllocItem(text, pixId); + maxItemCharacters = std::max(maxItemCharacters, static_cast<int>(strlen(text))); +} + +void ListBoxX::SetDelegate(IListBoxDelegate *lbDelegate) { + delegate = lbDelegate; +} + +void ListBoxX::SetList(const char *list, char separator, char typesep) { + // Turn off redraw while populating the list - this has a significant effect, even if + // the listbox is not visible. + SetRedraw(false); + Clear(); + const size_t size = strlen(list); + char *words = lti.SetWords(list); + const char *startword = words; + char *numword = nullptr; + for (size_t i=0; i < size; i++) { + if (words[i] == separator) { + words[i] = '\0'; + if (numword) + *numword = '\0'; + AppendListItem(startword, numword); + startword = words + i + 1; + numword = nullptr; + } else if (words[i] == typesep) { + numword = words + i; + } + } + if (startword) { + if (numword) + *numword = '\0'; + AppendListItem(startword, numword); + } + + // Finally populate the listbox itself with the correct number of items + const int count = lti.Count(); + ::SendMessage(lb, LB_INITSTORAGE, count, 0); + for (intptr_t j=0; j<count; j++) { + ListBox_AddItemData(lb, j+1); + } + SetRedraw(true); +} + +void ListBoxX::SetOptions(ListOptions options_) { + options = options_; + frameStyle = FlagSet(options.options, AutoCompleteOption::FixedSize) ? WS_BORDER : WS_THICKFRAME; +} + +void ListBoxX::AdjustWindowRect(PRectangle *rc, UINT dpiAdjust) const noexcept { + RECT rcw = RectFromPRectangle(*rc); + AdjustWindowRectForDpi(&rcw, frameStyle, dpiAdjust); + *rc = PRectangleFromRECT(rcw); +} + +int ListBoxX::ItemHeight() const noexcept { + int itemHeight = lineHeight + (TextInset.y * 2); + const int pixHeight = images.GetHeight() + (ImageInset.y * 2); + if (itemHeight < pixHeight) { + itemHeight = pixHeight; + } + return itemHeight; +} + +int ListBoxX::MinClientWidth() const noexcept { + return 12 * (aveCharWidth+aveCharWidth/3); +} + +POINT ListBoxX::MinTrackSize() const noexcept { + PRectangle rc = PRectangle::FromInts(0, 0, MinClientWidth(), ItemHeight()); + AdjustWindowRect(&rc, dpi); + POINT ret = {static_cast<LONG>(rc.Width()), static_cast<LONG>(rc.Height())}; + return ret; +} + +POINT ListBoxX::MaxTrackSize() const noexcept { + PRectangle rc = PRectangle::FromInts(0, 0, + std::max<int>(static_cast<unsigned int>(MinClientWidth()), + maxCharWidth * maxItemCharacters + TextInset.x * 2 + + TextOffset() + SystemMetricsForDpi(SM_CXVSCROLL, dpi)), + ItemHeight() * lti.Count()); + AdjustWindowRect(&rc, dpi); + POINT ret = {static_cast<LONG>(rc.Width()), static_cast<LONG>(rc.Height())}; + return ret; +} + +void ListBoxX::SetRedraw(bool on) noexcept { + ::SendMessage(lb, WM_SETREDRAW, on, 0); + if (on) { + ::RedrawWindow(lb, {}, {}, RDW_ERASE | RDW_FRAME | RDW_INVALIDATE | RDW_ALLCHILDREN); + } +} + +void ListBoxX::ResizeToCursor() { + PRectangle rc = GetPosition(); + POINT ptw; + ::GetCursorPos(&ptw); + const Point pt = PointFromPOINT(ptw) + dragOffset; + + switch (resizeHit) { + case HTLEFT: + rc.left = pt.x; + break; + case HTRIGHT: + rc.right = pt.x; + break; + case HTTOP: + rc.top = pt.y; + break; + case HTTOPLEFT: + rc.top = pt.y; + rc.left = pt.x; + break; + case HTTOPRIGHT: + rc.top = pt.y; + rc.right = pt.x; + break; + case HTBOTTOM: + rc.bottom = pt.y; + break; + case HTBOTTOMLEFT: + rc.bottom = pt.y; + rc.left = pt.x; + break; + case HTBOTTOMRIGHT: + rc.bottom = pt.y; + rc.right = pt.x; + break; + default: + break; + } + + const POINT ptMin = MinTrackSize(); + const POINT ptMax = MaxTrackSize(); + // We don't allow the left edge to move at present, but just in case + rc.left = std::clamp(rc.left, rcPreSize.right - ptMax.x, rcPreSize.right - ptMin.x); + rc.top = std::clamp(rc.top, rcPreSize.bottom - ptMax.y, rcPreSize.bottom - ptMin.y); + rc.right = std::clamp(rc.right, rcPreSize.left + ptMin.x, rcPreSize.left + ptMax.x); + rc.bottom = std::clamp(rc.bottom, rcPreSize.top + ptMin.y, rcPreSize.top + ptMax.y); + + SetPosition(rc); +} + +void ListBoxX::StartResize(WPARAM hitCode) { + rcPreSize = GetPosition(); + POINT cursorPos; + ::GetCursorPos(&cursorPos); + + switch (hitCode) { + case HTRIGHT: + case HTBOTTOM: + case HTBOTTOMRIGHT: + dragOffset.x = rcPreSize.right - cursorPos.x; + dragOffset.y = rcPreSize.bottom - cursorPos.y; + break; + + case HTTOPRIGHT: + dragOffset.x = rcPreSize.right - cursorPos.x; + dragOffset.y = rcPreSize.top - cursorPos.y; + break; + + // Note that the current hit test code prevents the left edge cases ever firing + // as we don't want the left edge to be movable + case HTLEFT: + case HTTOP: + case HTTOPLEFT: + dragOffset.x = rcPreSize.left - cursorPos.x; + dragOffset.y = rcPreSize.top - cursorPos.y; + break; + case HTBOTTOMLEFT: + dragOffset.x = rcPreSize.left - cursorPos.x; + dragOffset.y = rcPreSize.bottom - cursorPos.y; + break; + + default: + return; + } + + ::SetCapture(GetHWND()); + resizeHit = hitCode; +} + +LRESULT ListBoxX::NcHitTest(WPARAM wParam, LPARAM lParam) const { + const PRectangle rc = GetPosition(); + + LRESULT hit = ::DefWindowProc(GetHWND(), WM_NCHITTEST, wParam, lParam); + // There is an apparent bug in the DefWindowProc hit test code whereby it will + // return HTTOPXXX if the window in question is shorter than the default + // window caption height + frame, even if one is hovering over the bottom edge of + // the frame, so workaround that here + if (hit >= HTTOP && hit <= HTTOPRIGHT) { + const int minHeight = SystemMetricsForDpi(SM_CYMINTRACK, dpi); + const int yPos = GET_Y_LPARAM(lParam); + if ((rc.Height() < minHeight) && (yPos > ((rc.top + rc.bottom)/2))) { + hit += HTBOTTOM - HTTOP; + } + } + + // Never permit resizing that moves the left edge. Allow movement of top or bottom edge + // depending on whether the list is above or below the caret + switch (hit) { + case HTLEFT: + case HTTOPLEFT: + case HTBOTTOMLEFT: + hit = HTERROR; + break; + + case HTTOP: + case HTTOPRIGHT: { + // Valid only if caret below list + if (location.y < rc.top) + hit = HTERROR; + } + break; + + case HTBOTTOM: + case HTBOTTOMRIGHT: { + // Valid only if caret above list + if (rc.bottom <= location.y) + hit = HTERROR; + } + break; + default: + break; + } + + return hit; +} + +void ListBoxX::OnDoubleClick() { + if (delegate) { + ListBoxEvent event(ListBoxEvent::EventType::doubleClick); + delegate->ListNotify(&event); + } +} + +void ListBoxX::OnSelChange() { + if (delegate) { + ListBoxEvent event(ListBoxEvent::EventType::selectionChange); + delegate->ListNotify(&event); + } +} + +POINT ListBoxX::GetClientExtent() const noexcept { + RECT rc; + ::GetWindowRect(HwndFromWindowID(wid), &rc); + POINT ret { rc.right - rc.left, rc.bottom - rc.top }; + return ret; +} + +void ListBoxX::CentreItem(int n) { + // If below mid point, scroll up to centre, but with more items below if uneven + if (n >= 0) { + const POINT extent = GetClientExtent(); + const int visible = extent.y/ItemHeight(); + if (visible < Length()) { + const int top = ListBox_GetTopIndex(lb); + const int half = (visible - 1) / 2; + if (n > (top + half)) + ListBox_SetTopIndex(lb, n - half); + } + } +} + +void ListBoxX::AllocateBitMap() { + const SIZE extent { GetClientExtent().x, lineHeight }; + + graphics.bm.Create({}, extent.cx, -extent.cy, nullptr); + if (!graphics.bm) { + return; + } + + // Make a surface + graphics.pixmapLine = Surface::Allocate(technology); + graphics.pixmapLine->SetMode(SurfaceMode(codePage, false)); + +#if defined(USE_D2D) + if (technology != Technology::Default) { + if (!LoadD2D()) { + return; + } + + const D2D1_RENDER_TARGET_PROPERTIES drtp = D2D1::RenderTargetProperties( + D2D1_RENDER_TARGET_TYPE_DEFAULT, + { DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED }); + + HRESULT hr = CreateDCRenderTarget(&drtp, graphics.pBMDCTarget); + if (FAILED(hr) || !graphics.pBMDCTarget) { + return; + } + + const RECT rcExtent = { 0, 0, extent.cx, extent.cy }; + hr = graphics.pBMDCTarget->BindDC(graphics.bm.DC(), &rcExtent); + if (SUCCEEDED(hr)) { + graphics.pixmapLine->Init(graphics.pBMDCTarget.Get(), GetID()); + } + return; + } +#endif + + // Either technology == Technology::Default or USE_D2D turned off + graphics.pixmapLine->Init(graphics.bm.DC(), GetID()); +} + +LRESULT PASCAL ListBoxX::ControlWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) { + try { + ListBoxX *lbx = static_cast<ListBoxX *>(PointerFromWindow(::GetParent(hWnd))); + switch (iMessage) { + case WM_ERASEBKGND: + return TRUE; + + case WM_MOUSEACTIVATE: + // This prevents the view activating when the scrollbar is clicked + return MA_NOACTIVATE; + + case WM_LBUTTONDOWN: { + // We must take control of selection to prevent the ListBox activating + // the popup + const LRESULT lResult = ::SendMessage(hWnd, LB_ITEMFROMPOINT, 0, lParam); + if (HIWORD(lResult) == 0) { + ListBox_SetCurSel(hWnd, LOWORD(lResult)); + if (lbx) { + lbx->OnSelChange(); + } + } + } + return 0; + + case WM_LBUTTONUP: + return 0; + + case WM_LBUTTONDBLCLK: { + if (lbx) { + lbx->OnDoubleClick(); + } + } + return 0; + + case WM_MBUTTONDOWN: + // disable the scroll wheel button click action + return 0; + + default: + break; + } + + WNDPROC prevWndProc = reinterpret_cast<WNDPROC>(GetWindowLongPtr(hWnd, GWLP_USERDATA)); + if (prevWndProc) { + return ::CallWindowProc(prevWndProc, hWnd, iMessage, wParam, lParam); + } + return ::DefWindowProc(hWnd, iMessage, wParam, lParam); + } catch (...) { + } + return ::DefWindowProc(hWnd, iMessage, wParam, lParam); +} + +LRESULT ListBoxX::WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) { + switch (iMessage) { + case WM_CREATE: { + HINSTANCE hinstanceParent = GetWindowInstance(HwndFromWindow(*parent)); + // Note that LBS_NOINTEGRALHEIGHT is specified to fix cosmetic issue when resizing the list + // but has useful side effect of speeding up list population significantly + lb = ::CreateWindowEx( + 0, TEXT("listbox"), TEXT(""), + WS_CHILD | WS_VSCROLL | WS_VISIBLE | + LBS_OWNERDRAWFIXED | LBS_NODATA | LBS_NOINTEGRALHEIGHT, + 0, 0, 150,80, hWnd, + reinterpret_cast<HMENU>(static_cast<ptrdiff_t>(ctrlID)), + hinstanceParent, + nullptr); + WNDPROC prevWndProc = SubclassWindow(lb, ControlWndProc); + ::SetWindowLongPtr(lb, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(prevWndProc)); + } + break; + + case WM_SIZE: + if (lb) { + graphics.Release(); // Bitmap must be reallocated to new size. + SetRedraw(false); + ::SetWindowPos(lb, {}, 0, 0, LOWORD(lParam), HIWORD(lParam), SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE); + // Ensure the selection remains visible + CentreItem(GetSelection()); + SetRedraw(true); + } + break; + + case WM_PAINT: { + Painter painter(hWnd); + } + break; + + case WM_COMMAND: + // This is not actually needed now - the registered double click action is used + // directly to action a choice from the list. + ::SendMessage(HwndFromWindow(*parent), iMessage, wParam, lParam); + break; + + case WM_MEASUREITEM: { + MEASUREITEMSTRUCT *pMeasureItem = reinterpret_cast<MEASUREITEMSTRUCT *>(lParam); + pMeasureItem->itemHeight = ItemHeight(); + } + break; + + case WM_DRAWITEM: + Draw(reinterpret_cast<DRAWITEMSTRUCT *>(lParam)); + break; + + case WM_DESTROY: + lb = {}; + SetWindowPointer(hWnd, nullptr); + return ::DefWindowProc(hWnd, iMessage, wParam, lParam); + + case WM_ERASEBKGND: + // To reduce flicker we can elide background erasure since this window is + // completely covered by its child. + return TRUE; + + case WM_GETMINMAXINFO: { + MINMAXINFO *minMax = reinterpret_cast<MINMAXINFO*>(lParam); + minMax->ptMaxTrackSize = MaxTrackSize(); + minMax->ptMinTrackSize = MinTrackSize(); + } + break; + + case WM_MOUSEACTIVATE: + return MA_NOACTIVATE; + + case WM_NCHITTEST: + return NcHitTest(wParam, lParam); + + case WM_NCLBUTTONDOWN: + // We have to implement our own window resizing because the DefWindowProc + // implementation insists on activating the resized window + StartResize(wParam); + return 0; + + case WM_MOUSEMOVE: { + if (resizeHit == 0) { + return ::DefWindowProc(hWnd, iMessage, wParam, lParam); + } + ResizeToCursor(); + } + break; + + case WM_LBUTTONUP: + case WM_CANCELMODE: + if (resizeHit != 0) { + resizeHit = 0; + ::ReleaseCapture(); + } + return ::DefWindowProc(hWnd, iMessage, wParam, lParam); + case WM_MOUSEWHEEL: + if (wheelDelta.Accumulate(wParam)) { + const int nRows = GetVisibleRows(); + int linesToScroll = std::clamp(nRows - 1, 1, 3); + linesToScroll *= wheelDelta.Actions(); + int top = ListBox_GetTopIndex(lb) + linesToScroll; + if (top < 0) { + top = 0; + } + ListBox_SetTopIndex(lb, top); + } + break; + + default: + return ::DefWindowProc(hWnd, iMessage, wParam, lParam); + } + + return 0; +} + +LRESULT PASCAL ListBoxX::StaticWndProc( + HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) { + if (iMessage == WM_CREATE) { + CREATESTRUCT *pCreate = reinterpret_cast<CREATESTRUCT *>(lParam); + SetWindowPointer(hWnd, pCreate->lpCreateParams); + } + // Find C++ object associated with window. + ListBoxX *lbx = static_cast<ListBoxX *>(PointerFromWindow(hWnd)); + if (lbx) { + return lbx->WndProc(hWnd, iMessage, wParam, lParam); + } + return ::DefWindowProc(hWnd, iMessage, wParam, lParam); +} + +namespace Scintilla::Internal { + +bool ListBoxX_Register() noexcept { + WNDCLASSEX wndclassc {}; + wndclassc.cbSize = sizeof(wndclassc); + // We need CS_HREDRAW and CS_VREDRAW because of the ellipsis that might be drawn for + // truncated items in the list and the appearance/disappearance of the vertical scroll bar. + // The list repaint is double-buffered to avoid the flicker this would otherwise cause. + wndclassc.style = CS_GLOBALCLASS | CS_HREDRAW | CS_VREDRAW; + wndclassc.cbWndExtra = sizeof(ListBoxX *); + wndclassc.hInstance = hinstPlatformRes; + wndclassc.lpfnWndProc = ListBoxX::StaticWndProc; + wndclassc.hCursor = ::LoadCursor({}, IDC_ARROW); + wndclassc.lpszClassName = ListBoxX_ClassName; + + return ::RegisterClassEx(&wndclassc) != 0; +} + +void ListBoxX_Unregister() noexcept { + if (hinstPlatformRes) { + ::UnregisterClass(ListBoxX_ClassName, hinstPlatformRes); + } +} + +ListBox::ListBox() noexcept = default; + +ListBox::~ListBox() noexcept = default; + +} diff --git a/win32/ListBox.h b/win32/ListBox.h new file mode 100644 index 000000000..cb3a17479 --- /dev/null +++ b/win32/ListBox.h @@ -0,0 +1,18 @@ +// Scintilla source code edit control +/** @file ListBox.h + ** Definitions for list box on Windows. + **/ +// Copyright 2025 by Neil Hodgson <neilh@scintilla.org> +// The License.txt file describes the conditions under which this software may be distributed. + +#ifndef LISTBOX_H +#define LISTBOX_H + +namespace Scintilla::Internal { + +bool ListBoxX_Register() noexcept; +void ListBoxX_Unregister() noexcept; + +} + +#endif diff --git a/win32/PlatWin.cxx b/win32/PlatWin.cxx index 6a4f9d146..f14661164 100644 --- a/win32/PlatWin.cxx +++ b/win32/PlatWin.cxx @@ -63,220 +63,16 @@ using Microsoft::WRL::ComPtr; #include "WinTypes.h" #include "PlatWin.h" - -// __uuidof is a Microsoft extension but makes COM code neater, so disable warning -#if defined(__clang__) -#pragma clang diagnostic ignored "-Wlanguage-extension-token" +#include "ListBox.h" +#if defined(USE_D2D) +#include "SurfaceD2D.h" #endif using namespace Scintilla; namespace Scintilla::Internal { -UINT CodePageFromCharSet(CharacterSet characterSet, UINT documentCodePage) noexcept; - -#if defined(USE_D2D) -IDWriteFactory1 *pIDWriteFactory = nullptr; -ID2D1Factory1 *pD2DFactory = nullptr; -D2D1_DRAW_TEXT_OPTIONS d2dDrawTextOptions = D2D1_DRAW_TEXT_OPTIONS_NONE; - -namespace { - -HMODULE hDLLD2D{}; -HMODULE hDLLD3D{}; -HMODULE hDLLDWrite{}; - -PFN_D3D11_CREATE_DEVICE fnDCD {}; - -void LoadD2DOnce() noexcept { - DWORD loadLibraryFlags = 0; - HMODULE kernel32 = ::GetModuleHandleW(L"kernel32.dll"); - if (kernel32) { - if (::GetProcAddress(kernel32, "SetDefaultDllDirectories")) { - // Availability of SetDefaultDllDirectories implies Windows 8+ or - // that KB2533623 has been installed so LoadLibraryEx can be called - // with LOAD_LIBRARY_SEARCH_SYSTEM32. - loadLibraryFlags = LOAD_LIBRARY_SEARCH_SYSTEM32; - } - } - - using D2D1CFSig = HRESULT (WINAPI *)(D2D1_FACTORY_TYPE factoryType, REFIID riid, - CONST D2D1_FACTORY_OPTIONS *pFactoryOptions, IUnknown **factory); - using DWriteCFSig = HRESULT (WINAPI *)(DWRITE_FACTORY_TYPE factoryType, REFIID iid, - IUnknown **factory); - - hDLLD2D = ::LoadLibraryEx(TEXT("D2D1.DLL"), {}, loadLibraryFlags); - D2D1CFSig fnD2DCF = DLLFunction<D2D1CFSig>(hDLLD2D, "D2D1CreateFactory"); - if (fnD2DCF) { - const D2D1_FACTORY_OPTIONS options {}; - // A multi threaded factory in case Scintilla is used with multiple GUI threads - fnD2DCF(D2D1_FACTORY_TYPE_MULTI_THREADED, - __uuidof(ID2D1Factory1), - &options, - reinterpret_cast<IUnknown**>(&pD2DFactory)); - } - hDLLDWrite = ::LoadLibraryEx(TEXT("DWRITE.DLL"), {}, loadLibraryFlags); - DWriteCFSig fnDWCF = DLLFunction<DWriteCFSig>(hDLLDWrite, "DWriteCreateFactory"); - if (fnDWCF) { - const GUID IID_IDWriteFactory2 = // 0439fc60-ca44-4994-8dee-3a9af7b732ec - { 0x0439fc60, 0xca44, 0x4994, { 0x8d, 0xee, 0x3a, 0x9a, 0xf7, 0xb7, 0x32, 0xec } }; - - const HRESULT hr = fnDWCF(DWRITE_FACTORY_TYPE_SHARED, - IID_IDWriteFactory2, - reinterpret_cast<IUnknown**>(&pIDWriteFactory)); - if (SUCCEEDED(hr)) { - // D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT - d2dDrawTextOptions = static_cast<D2D1_DRAW_TEXT_OPTIONS>(0x00000004); - } else { - fnDWCF(DWRITE_FACTORY_TYPE_SHARED, - __uuidof(IDWriteFactory1), - reinterpret_cast<IUnknown**>(&pIDWriteFactory)); - } - } - - hDLLD3D = ::LoadLibraryEx(TEXT("D3D11.DLL"), {}, loadLibraryFlags); - if (!hDLLD3D) { - Platform::DebugPrintf("Direct3D not loaded\n"); - } - fnDCD = DLLFunction<PFN_D3D11_CREATE_DEVICE>(hDLLD3D, "D3D11CreateDevice"); - if (!fnDCD) { - Platform::DebugPrintf("Direct3D does not have D3D11CreateDevice\n"); - } -} - -} - -HRESULT CreateD3D(D3D11Device &device) noexcept { - device = nullptr; - if (!fnDCD) { - return E_FAIL; - } - - const D3D_FEATURE_LEVEL featureLevels[] = { - D3D_FEATURE_LEVEL_11_1, - D3D_FEATURE_LEVEL_11_0, - D3D_FEATURE_LEVEL_10_1, - D3D_FEATURE_LEVEL_10_0, - D3D_FEATURE_LEVEL_9_3, - D3D_FEATURE_LEVEL_9_2, - D3D_FEATURE_LEVEL_9_1 - }; - - // Create device. - // Try for a hardware device but, if that fails, fall back to the Warp software rasterizer. - ComPtr<ID3D11Device> upDevice; - HRESULT hr = S_OK; - const D3D_DRIVER_TYPE typesToTry[] = { D3D_DRIVER_TYPE_HARDWARE, D3D_DRIVER_TYPE_WARP }; - for (const D3D_DRIVER_TYPE type : typesToTry) { - hr = fnDCD(nullptr, - type, - {}, - D3D11_CREATE_DEVICE_BGRA_SUPPORT, - featureLevels, - ARRAYSIZE(featureLevels), - D3D11_SDK_VERSION, - upDevice.GetAddressOf(), - nullptr, - nullptr); - if (SUCCEEDED(hr)) - break; - } - if (FAILED(hr)) { - Platform::DebugPrintf("Failed to create D3D11 device 0x%lx\n", hr); - return hr; - } - - // Convert from D3D11 to D3D11.1 - hr = upDevice.As(&device); - if (FAILED(hr)) { - Platform::DebugPrintf("Failed to create D3D11.1 device 0x%lx\n", hr); - } - return hr; -} - -bool LoadD2D() noexcept { - static std::once_flag once; - try { - std::call_once(once, LoadD2DOnce); - } catch (...) { - // ignore - } - return pIDWriteFactory && pD2DFactory; -} - -HRESULT CreateDCRenderTarget(const D2D1_RENDER_TARGET_PROPERTIES *renderTargetProperties, DCRenderTarget &dcRT) noexcept { - return pD2DFactory->CreateDCRenderTarget(renderTargetProperties, dcRT.ReleaseAndGetAddressOf()); -} - -namespace { - -constexpr D2D_COLOR_F ColorFromColourAlpha(ColourRGBA colour) noexcept { - return D2D_COLOR_F{ - colour.GetRedComponent(), - colour.GetGreenComponent(), - colour.GetBlueComponent(), - colour.GetAlphaComponent() - }; -} - -using BrushSolid = ComPtr<ID2D1SolidColorBrush>; - -BrushSolid BrushSolidCreate(ID2D1RenderTarget *pTarget, COLORREF colour) noexcept { - BrushSolid brush; - const D2D_COLOR_F col = ColorFromColourAlpha(ColourRGBA::FromRGB(colour)); - if (FAILED(pTarget->CreateSolidColorBrush(col, brush.GetAddressOf()))) { - return {}; - } - return brush; -} - -using Geometry = ComPtr<ID2D1PathGeometry>; - -Geometry GeometryCreate() noexcept { - Geometry geometry; - if (FAILED(pD2DFactory->CreatePathGeometry(geometry.GetAddressOf()))) { - return {}; - } - return geometry; -} - -using GeometrySink = ComPtr<ID2D1GeometrySink>; - -GeometrySink GeometrySinkCreate(ID2D1PathGeometry *geometry) noexcept { - GeometrySink sink; - if (FAILED(geometry->Open(sink.GetAddressOf()))) { - return {}; - } - return sink; -} - -using StrokeStyle = ComPtr<ID2D1StrokeStyle>; - -StrokeStyle StrokeStyleCreate(const D2D1_STROKE_STYLE_PROPERTIES &strokeStyleProperties) noexcept { - StrokeStyle strokeStyle; - const HRESULT hr = pD2DFactory->CreateStrokeStyle( - strokeStyleProperties, nullptr, 0, strokeStyle.GetAddressOf()); - if (FAILED(hr)) { - return {}; - } - return strokeStyle; -} - -using TextLayout = ComPtr<IDWriteTextLayout>; - -TextLayout LayoutCreate(std::wstring_view wsv, IDWriteTextFormat *pTextFormat, FLOAT maxWidth=10000.0F, FLOAT maxHeight=1000.0F) noexcept { - TextLayout layout; - const HRESULT hr = pIDWriteFactory->CreateTextLayout(wsv.data(), static_cast<UINT32>(wsv.length()), - pTextFormat, maxWidth, maxHeight, layout.GetAddressOf()); - if (FAILED(hr)) { - return {}; - } - return layout; -} - -} - -#endif +HINSTANCE hinstPlatformRes{}; void *PointerFromWindow(HWND hWnd) noexcept { return reinterpret_cast<void *>(::GetWindowLongPtr(hWnd, 0)); @@ -294,8 +90,8 @@ UINT uSystemDPI = USER_DEFAULT_SCREEN_DPI; using GetDpiForWindowSig = UINT(WINAPI *)(HWND hwnd); GetDpiForWindowSig fnGetDpiForWindow = nullptr; -HMODULE hDLLShcore {}; -using GetDpiForMonitorSig = HRESULT (WINAPI *)(HMONITOR hmonitor, /*MONITOR_DPI_TYPE*/int dpiType, UINT *dpiX, UINT *dpiY); +HMODULE hDLLShcore{}; +using GetDpiForMonitorSig = HRESULT(WINAPI *)(HMONITOR hmonitor, /*MONITOR_DPI_TYPE*/int dpiType, UINT *dpiX, UINT *dpiY); GetDpiForMonitorSig fnGetDpiForMonitor = nullptr; using GetSystemMetricsForDpiSig = int(WINAPI *)(int nIndex, UINT dpi); @@ -343,206 +139,6 @@ void LoadDpiForWindow() noexcept { } } -HINSTANCE hinstPlatformRes {}; - -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; - } -} - -#if defined(USE_D2D) -constexpr D2D1_TEXT_ANTIALIAS_MODE DWriteMapFontQuality(FontQuality extraFontFlag) noexcept { - switch (extraFontFlag & FontQuality::QualityMask) { - - case FontQuality::QualityNonAntialiased: - return D2D1_TEXT_ANTIALIAS_MODE_ALIASED; - - case FontQuality::QualityAntialiased: - return D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE; - - case FontQuality::QualityLcdOptimized: - return D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE; - - default: - return D2D1_TEXT_ANTIALIAS_MODE_DEFAULT; - } -} -#endif - -// Both GDI and DirectWrite can produce a HFONT for use in list boxes -struct FontWin : public Font { - [[nodiscard]] virtual HFONT HFont() const noexcept = 0; - [[nodiscard]] virtual std::unique_ptr<FontWin> Duplicate() const = 0; - [[nodiscard]] virtual CharacterSet GetCharacterSet() const noexcept = 0; -}; - -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; - } -}; - -#if defined(USE_D2D) -struct FontDirectWrite : public FontWin { - ComPtr<IDWriteTextFormat> pTextFormat; - FontQuality extraFontFlag = FontQuality::QualityDefault; - CharacterSet characterSet = CharacterSet::Ansi; - FLOAT yAscent = 2.0f; - FLOAT yDescent = 1.0f; - FLOAT yInternalLeading = 0.0f; - - explicit FontDirectWrite(const FontParameters &fp) : - extraFontFlag(fp.extraFontFlag), - characterSet(fp.characterSet) { - const std::wstring wsFace = WStringFromUTF8(fp.faceName); - const std::wstring wsLocale = WStringFromUTF8(fp.localeName); - const FLOAT fHeight = static_cast<FLOAT>(fp.size); - const DWRITE_FONT_STYLE style = fp.italic ? DWRITE_FONT_STYLE_ITALIC : DWRITE_FONT_STYLE_NORMAL; - HRESULT hr = pIDWriteFactory->CreateTextFormat(wsFace.c_str(), nullptr, - static_cast<DWRITE_FONT_WEIGHT>(fp.weight), - style, - static_cast<DWRITE_FONT_STRETCH>(fp.stretch), - fHeight, wsLocale.c_str(), pTextFormat.GetAddressOf()); - if (hr == E_INVALIDARG) { - // Possibly a bad locale name like "/" so try "en-us". - hr = pIDWriteFactory->CreateTextFormat(wsFace.c_str(), nullptr, - static_cast<DWRITE_FONT_WEIGHT>(fp.weight), - style, - static_cast<DWRITE_FONT_STRETCH>(fp.stretch), - fHeight, L"en-us", pTextFormat.ReleaseAndGetAddressOf()); - } - if (SUCCEEDED(hr)) { - pTextFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP); - - if (TextLayout pTextLayout = LayoutCreate(L"X", pTextFormat.Get())) { - constexpr int maxLines = 2; - DWRITE_LINE_METRICS lineMetrics[maxLines]{}; - UINT32 lineCount = 0; - hr = pTextLayout->GetLineMetrics(lineMetrics, maxLines, &lineCount); - if (SUCCEEDED(hr)) { - yAscent = lineMetrics[0].baseline; - yDescent = lineMetrics[0].height - lineMetrics[0].baseline; - - FLOAT emHeight; - hr = pTextLayout->GetFontSize(0, &emHeight); - if (SUCCEEDED(hr)) { - yInternalLeading = lineMetrics[0].height - emHeight; - } - } - pTextFormat->SetLineSpacing(DWRITE_LINE_SPACING_METHOD_UNIFORM, lineMetrics[0].height, lineMetrics[0].baseline); - } - } - } - // Allow copy constructor. Has to explicitly copy each field since can't use =default as deleted in Font. - FontDirectWrite(const FontDirectWrite &other) noexcept { - pTextFormat = other.pTextFormat; - extraFontFlag = other.extraFontFlag; - characterSet = other.characterSet; - yAscent = other.yAscent; - yDescent = other.yDescent; - yInternalLeading = other.yInternalLeading; - } - // Deleted so FontDirectWrite objects can not be copied. - FontDirectWrite(FontDirectWrite &&) = delete; - FontDirectWrite &operator=(const FontDirectWrite &) = delete; - FontDirectWrite &operator=(FontDirectWrite &&) = delete; - ~FontDirectWrite() noexcept override = default; - [[nodiscard]] HFONT HFont() const noexcept override { - LOGFONTW lf = {}; - const HRESULT hr = pTextFormat->GetFontFamilyName(lf.lfFaceName, LF_FACESIZE); - if (!SUCCEEDED(hr)) { - return {}; - } - lf.lfWeight = pTextFormat->GetFontWeight(); - lf.lfItalic = pTextFormat->GetFontStyle() == DWRITE_FONT_STYLE_ITALIC; - lf.lfHeight = -static_cast<int>(pTextFormat->GetFontSize()); - return ::CreateFontIndirectW(&lf); - } - - [[nodiscard]] int CodePageText(int codePage) const noexcept { - if (!(codePage == CpUtf8) && (characterSet != CharacterSet::Ansi)) { - codePage = CodePageFromCharSet(characterSet, codePage); - } - return codePage; - } - - static const FontDirectWrite *Cast(const Font *font_) { - const FontDirectWrite *pfm = dynamic_cast<const FontDirectWrite *>(font_); - PLATFORM_ASSERT(pfm); - if (!pfm) { - throw std::runtime_error("SurfaceD2D::SetFont: wrong Font type."); - } - return pfm; - } - - [[nodiscard]] std::unique_ptr<FontWin> Duplicate() const override { - return std::make_unique<FontDirectWrite>(*this); - } - - [[nodiscard]] CharacterSet GetCharacterSet() const noexcept override { - return characterSet; - } -}; -#endif - } HMONITOR MonitorFromWindowHandleScaling(HWND hWnd) noexcept { @@ -578,64 +174,6 @@ float GetDeviceScaleFactorWhenGdiScalingActive(HWND hWnd) noexcept { return 1.f; } -std::shared_ptr<Font> Font::Allocate(const FontParameters &fp) { -#if defined(USE_D2D) - if (fp.technology != Technology::Default) { - return std::make_shared<FontDirectWrite>(fp); - } -#endif - return std::make_shared<FontGDI>(fp); -} - -// Buffer to hold strings and string position arrays without always allocating on heap. -// May sometimes have string too long to allocate on stack. So use a fixed stack-allocated buffer -// when less than safe size otherwise allocate on heap and free automatically. -template<typename T, int lengthStandard> -class VarBuffer { - T bufferStandard[lengthStandard]; -public: - T *buffer; - explicit VarBuffer(size_t length) : buffer(nullptr) { - if (length > lengthStandard) { - buffer = new T[length]; - } else { - buffer = bufferStandard; - } - } - // Deleted so VarBuffer objects can not be copied. - VarBuffer(const VarBuffer &) = delete; - VarBuffer(VarBuffer &&) = delete; - VarBuffer &operator=(const VarBuffer &) = delete; - VarBuffer &operator=(VarBuffer &&) = delete; - - ~VarBuffer() noexcept { - if (buffer != bufferStandard) { - delete []buffer; - buffer = nullptr; - } - } -}; - -constexpr int stackBufferLength = 400; -class TextWide : public VarBuffer<wchar_t, stackBufferLength> { -public: - int tlen; // Using int instead of size_t as most Win32 APIs take int. - TextWide(std::string_view text, int codePage) : - VarBuffer<wchar_t, stackBufferLength>(text.length()) { - if (codePage == CpUtf8) { - tlen = static_cast<int>(UTF16FromUTF8(text, buffer, text.length())); - } else { - // Support Asian string display in 9x English - tlen = ::MultiByteToWideChar(codePage, 0, text.data(), static_cast<int>(text.length()), - buffer, static_cast<int>(text.length())); - } - } - [[nodiscard]] std::wstring_view AsView() const noexcept { - return std::wstring_view(buffer, tlen); - } -}; -using TextPositions = VarBuffer<XYPOSITION, stackBufferLength>; - UINT DpiForWindow(WindowID wid) noexcept { if (fnGetDpiForWindow) { return fnGetDpiForWindow(HwndFromWindowID(wid)); @@ -661,314 +199,12 @@ int SystemMetricsForDpi(int nIndex, UINT dpi) noexcept { return value; } -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); +void AdjustWindowRectForDpi(LPRECT lpRect, DWORD dwStyle, UINT dpi) noexcept { + if (fnAdjustWindowRectExForDpi) { + fnAdjustWindowRectExForDpi(lpRect, dwStyle, false, WS_EX_WINDOWEDGE, dpi); } 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)); + ::AdjustWindowRectEx(lpRect, dwStyle, false, WS_EX_WINDOWEDGE); } - 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); } namespace { @@ -1008,50 +244,8 @@ HBITMAP BitMapSection(HDC hdc, int width, int height, DWORD **pixels) noexcept { return hbm; } -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()); -} - -// Manage the lifetime of a memory HBITMAP and its HDC so there are no leaks. -class GDIBitMap { - HDC hdc{}; - HBITMAP hbm{}; - HBITMAP hbmOriginal{}; - -public: - GDIBitMap() noexcept = default; - // Deleted so GDIBitMap objects can not be copied. - GDIBitMap(const GDIBitMap &) = delete; - GDIBitMap(GDIBitMap &&) = delete; - // Move would be OK but not needed yet - GDIBitMap &operator=(const GDIBitMap &) = delete; - GDIBitMap &operator=(GDIBitMap &&) = delete; - ~GDIBitMap() noexcept; - - void Create(HDC hdcBase, int width, int height, DWORD **pixels) noexcept; - void Release() noexcept; - HBITMAP Extract() noexcept; - - [[nodiscard]] HDC DC() const noexcept { - return hdc; - } - [[nodiscard]] explicit operator bool() const noexcept { - return hdc && hbm; - } -}; - GDIBitMap::~GDIBitMap() noexcept { Release(); } @@ -1098,1763 +292,6 @@ HBITMAP GDIBitMap::Extract() noexcept { return ret; } -// 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 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() { -} - -#if defined(USE_D2D) - -namespace { - -constexpr D2D1_RECT_F RectangleFromPRectangle(PRectangle rc) noexcept { - return { - static_cast<FLOAT>(rc.left), - static_cast<FLOAT>(rc.top), - static_cast<FLOAT>(rc.right), - static_cast<FLOAT>(rc.bottom) - }; -} - -constexpr D2D1_POINT_2F DPointFromPoint(Point point) noexcept { - return { static_cast<FLOAT>(point.x), static_cast<FLOAT>(point.y) }; -} - -constexpr Supports SupportsD2D[] = { - Supports::LineDrawsFinal, - Supports::FractionalStrokeWidth, - Supports::TranslucentStroke, - Supports::PixelModification, - Supports::ThreadSafeMeasureWidths, -}; - -constexpr D2D1_RECT_F RectangleInset(D2D1_RECT_F rect, FLOAT inset) noexcept { - return D2D1_RECT_F{ - rect.left + inset, - rect.top + inset, - rect.right - inset, - rect.bottom - inset }; -} - -} - -class BlobInline; - -class SurfaceD2D : public Surface, public ISetRenderingParams { - SurfaceMode mode; - - // Text measuring surface: both pRenderTarget and pBitmapRenderTarget are null. - // Window surface: pRenderTarget is valid but not pBitmapRenderTarget. - // Bitmap drawing surface: both pRenderTarget and pBitmapRenderTarget are valid and the same. - ComPtr<ID2D1RenderTarget> pRenderTarget; - ComPtr<ID2D1BitmapRenderTarget> pBitmapRenderTarget; - int clipsActive = 0; - - BrushSolid pBrush = nullptr; - - static constexpr FontQuality invalidFontQuality = FontQuality::QualityMask; - FontQuality fontQuality = invalidFontQuality; - int logPixelsY = USER_DEFAULT_SCREEN_DPI; - int deviceScaleFactor = 1; - std::shared_ptr<RenderingParams> renderingParams; - - void Clear() noexcept; - void SetFontQuality(FontQuality extraFontFlag); - HRESULT GetBitmap(ID2D1Bitmap **ppBitmap); - void SetDeviceScaleFactor(const ID2D1RenderTarget *const pD2D1RenderTarget) noexcept; - -public: - SurfaceD2D() noexcept = default; - SurfaceD2D(ID2D1RenderTarget *pRenderTargetCompatible, int width, int height, SurfaceMode mode_, int logPixelsY_) noexcept; - // Deleted so SurfaceD2D objects can not be copied. - SurfaceD2D(const SurfaceD2D &) = delete; - SurfaceD2D(SurfaceD2D &&) = delete; - SurfaceD2D &operator=(const SurfaceD2D &) = delete; - SurfaceD2D &operator=(SurfaceD2D &&) = delete; - ~SurfaceD2D() noexcept override; - - void SetScale(WindowID wid) noexcept; - 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; - - void D2DPenColourAlpha(ColourRGBA fore) noexcept; - int LogPixelsY() override; - int PixelDivisions() override; - int DeviceHeightFont(int points) override; - void LineDraw(Point start, Point end, Stroke stroke) override; - static Geometry GeometricFigure(const Point *pts, size_t npts, D2D1_FIGURE_BEGIN figureBegin) noexcept; - 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, int codePageOverride, 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 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; - - void SetRenderingParams(std::shared_ptr<RenderingParams> renderingParams_) override; -}; - -SurfaceD2D::SurfaceD2D(ID2D1RenderTarget *pRenderTargetCompatible, int width, int height, SurfaceMode mode_, int logPixelsY_) noexcept { - const D2D1_SIZE_F desiredSize = D2D1::SizeF(static_cast<float>(width), static_cast<float>(height)); - D2D1_PIXEL_FORMAT desiredFormat; -#ifdef __MINGW32__ - desiredFormat.format = DXGI_FORMAT_UNKNOWN; -#else - desiredFormat = pRenderTargetCompatible->GetPixelFormat(); -#endif - desiredFormat.alphaMode = D2D1_ALPHA_MODE_IGNORE; - const HRESULT hr = pRenderTargetCompatible->CreateCompatibleRenderTarget( - &desiredSize, nullptr, &desiredFormat, D2D1_COMPATIBLE_RENDER_TARGET_OPTIONS_NONE, pBitmapRenderTarget.GetAddressOf()); - if (SUCCEEDED(hr)) { - pRenderTarget = pBitmapRenderTarget; - SetDeviceScaleFactor(pRenderTarget.Get()); - pRenderTarget->BeginDraw(); - } - mode = mode_; - logPixelsY = logPixelsY_; -} - -SurfaceD2D::~SurfaceD2D() noexcept { - Clear(); -} - -void SurfaceD2D::Clear() noexcept { - pBrush = nullptr; - if (pRenderTarget) { - while (clipsActive) { - pRenderTarget->PopAxisAlignedClip(); - clipsActive--; - } - if (pBitmapRenderTarget) { - pRenderTarget->EndDraw(); - } - } - pRenderTarget = nullptr; - pBitmapRenderTarget = nullptr; -} - -void SurfaceD2D::Release() noexcept { - Clear(); -} - -void SurfaceD2D::SetScale(WindowID wid) noexcept { - fontQuality = invalidFontQuality; - logPixelsY = DpiForWindow(wid); -} - -int SurfaceD2D::SupportsFeature(Supports feature) noexcept { - for (const Supports f : SupportsD2D) { - if (f == feature) - return 1; - } - return 0; -} - -bool SurfaceD2D::Initialised() { - return pRenderTarget; -} - -void SurfaceD2D::Init(WindowID wid) { - Release(); - SetScale(wid); -} - -void SurfaceD2D::Init(SurfaceID sid, WindowID wid) { - Release(); - SetScale(wid); - pRenderTarget = static_cast<ID2D1RenderTarget *>(sid); - SetDeviceScaleFactor(pRenderTarget.Get()); -} - -std::unique_ptr<Surface> SurfaceD2D::AllocatePixMap(int width, int height) { - std::unique_ptr<SurfaceD2D> surf = std::make_unique<SurfaceD2D>(pRenderTarget.Get(), width, height, mode, logPixelsY); - surf->SetRenderingParams(renderingParams); - return surf; -} - -void SurfaceD2D::SetMode(SurfaceMode mode_) { - mode = mode_; -} - -HRESULT SurfaceD2D::GetBitmap(ID2D1Bitmap **ppBitmap) { - PLATFORM_ASSERT(pBitmapRenderTarget); - return pBitmapRenderTarget->GetBitmap(ppBitmap); -} - -void SurfaceD2D::D2DPenColourAlpha(ColourRGBA fore) noexcept { - if (pRenderTarget) { - const D2D_COLOR_F col = ColorFromColourAlpha(fore); - if (pBrush) { - pBrush->SetColor(col); - } else { - const HRESULT hr = pRenderTarget->CreateSolidColorBrush(col, &pBrush); - if (!SUCCEEDED(hr)) { - pBrush = nullptr; - } - } - } -} - -void SurfaceD2D::SetFontQuality(FontQuality extraFontFlag) { - if ((fontQuality != extraFontFlag) && renderingParams) { - fontQuality = extraFontFlag; - const D2D1_TEXT_ANTIALIAS_MODE aaMode = DWriteMapFontQuality(extraFontFlag); - if (aaMode == D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE && renderingParams->customRenderingParams) { - pRenderTarget->SetTextRenderingParams(renderingParams->customRenderingParams.Get()); - } else if (renderingParams->defaultRenderingParams) { - pRenderTarget->SetTextRenderingParams(renderingParams->defaultRenderingParams.Get()); - } - pRenderTarget->SetTextAntialiasMode(aaMode); - } -} - -int SurfaceD2D::LogPixelsY() { - return logPixelsY; -} - -void SurfaceD2D::SetDeviceScaleFactor(const ID2D1RenderTarget *const pD2D1RenderTarget) noexcept { - FLOAT dpiX = 0.f; - FLOAT dpiY = 0.f; - pD2D1RenderTarget->GetDpi(&dpiX, &dpiY); - deviceScaleFactor = static_cast<int>(dpiX / dpiDefault); -} - -int SurfaceD2D::PixelDivisions() { - return deviceScaleFactor; -} - -int SurfaceD2D::DeviceHeightFont(int points) { - return ::MulDiv(points, LogPixelsY(), 72); -} - -void SurfaceD2D::LineDraw(Point start, Point end, Stroke stroke) { - D2DPenColourAlpha(stroke.colour); - - D2D1_STROKE_STYLE_PROPERTIES strokeProps {}; - strokeProps.startCap = D2D1_CAP_STYLE_SQUARE; - strokeProps.endCap = D2D1_CAP_STYLE_SQUARE; - strokeProps.dashCap = D2D1_CAP_STYLE_FLAT; - strokeProps.lineJoin = D2D1_LINE_JOIN_MITER; - strokeProps.miterLimit = 4.0f; - strokeProps.dashStyle = D2D1_DASH_STYLE_SOLID; - strokeProps.dashOffset = 0; - - // get the stroke style to apply - if (const StrokeStyle pStrokeStyle = StrokeStyleCreate(strokeProps)) { - pRenderTarget->DrawLine( - DPointFromPoint(start), - DPointFromPoint(end), pBrush.Get(), stroke.WidthF(), pStrokeStyle.Get()); - } -} - -Geometry SurfaceD2D::GeometricFigure(const Point *pts, size_t npts, D2D1_FIGURE_BEGIN figureBegin) noexcept { - Geometry geometry = GeometryCreate(); - if (geometry) { - if (const GeometrySink sink = GeometrySinkCreate(geometry.Get())) { - sink->BeginFigure(DPointFromPoint(pts[0]), figureBegin); - for (size_t i = 1; i < npts; i++) { - sink->AddLine(DPointFromPoint(pts[i])); - } - sink->EndFigure((figureBegin == D2D1_FIGURE_BEGIN_FILLED) ? - D2D1_FIGURE_END_CLOSED : D2D1_FIGURE_END_OPEN); - sink->Close(); - } - } - return geometry; -} - -void SurfaceD2D::PolyLine(const Point *pts, size_t npts, Stroke stroke) { - PLATFORM_ASSERT(pRenderTarget && (npts > 1)); - if (!pRenderTarget || (npts <= 1)) { - return; - } - - const Geometry geometry = GeometricFigure(pts, npts, D2D1_FIGURE_BEGIN_HOLLOW); - PLATFORM_ASSERT(geometry); - if (!geometry) { - return; - } - - D2DPenColourAlpha(stroke.colour); - D2D1_STROKE_STYLE_PROPERTIES strokeProps {}; - strokeProps.startCap = D2D1_CAP_STYLE_ROUND; - strokeProps.endCap = D2D1_CAP_STYLE_ROUND; - strokeProps.dashCap = D2D1_CAP_STYLE_FLAT; - strokeProps.lineJoin = D2D1_LINE_JOIN_MITER; - strokeProps.miterLimit = 4.0f; - strokeProps.dashStyle = D2D1_DASH_STYLE_SOLID; - strokeProps.dashOffset = 0; - - // get the stroke style to apply - if (const StrokeStyle pStrokeStyle = StrokeStyleCreate(strokeProps)) { - pRenderTarget->DrawGeometry(geometry.Get(), pBrush.Get(), stroke.WidthF(), pStrokeStyle.Get()); - } -} - -void SurfaceD2D::Polygon(const Point *pts, size_t npts, FillStroke fillStroke) { - PLATFORM_ASSERT(pRenderTarget && (npts > 2)); - if (pRenderTarget) { - const Geometry geometry = GeometricFigure(pts, npts, D2D1_FIGURE_BEGIN_FILLED); - PLATFORM_ASSERT(geometry); - if (geometry) { - D2DPenColourAlpha(fillStroke.fill.colour); - pRenderTarget->FillGeometry(geometry.Get(), pBrush.Get()); - D2DPenColourAlpha(fillStroke.stroke.colour); - pRenderTarget->DrawGeometry(geometry.Get(), pBrush.Get(), fillStroke.stroke.WidthF()); - } - } -} - -void SurfaceD2D::RectangleDraw(PRectangle rc, FillStroke fillStroke) { - if (!pRenderTarget) - return; - const D2D1_RECT_F rect = RectangleFromPRectangle(rc); - const D2D1_RECT_F rectFill = RectangleInset(rect, fillStroke.stroke.WidthF()); - const float halfStroke = fillStroke.stroke.WidthF() / 2.0f; - const D2D1_RECT_F rectOutline = RectangleInset(rect, halfStroke); - - D2DPenColourAlpha(fillStroke.fill.colour); - pRenderTarget->FillRectangle(&rectFill, pBrush.Get()); - D2DPenColourAlpha(fillStroke.stroke.colour); - pRenderTarget->DrawRectangle(&rectOutline, pBrush.Get(), fillStroke.stroke.WidthF()); -} - -void SurfaceD2D::RectangleFrame(PRectangle rc, Stroke stroke) { - if (pRenderTarget) { - const XYPOSITION halfStroke = stroke.width / 2.0f; - const D2D1_RECT_F rectangle1 = RectangleFromPRectangle(rc.Inset(halfStroke)); - D2DPenColourAlpha(stroke.colour); - pRenderTarget->DrawRectangle(&rectangle1, pBrush.Get(), stroke.WidthF()); - } -} - -void SurfaceD2D::FillRectangle(PRectangle rc, Fill fill) { - if (pRenderTarget) { - D2DPenColourAlpha(fill.colour); - const D2D1_RECT_F rectangle = RectangleFromPRectangle(rc); - pRenderTarget->FillRectangle(&rectangle, pBrush.Get()); - } -} - -void SurfaceD2D::FillRectangleAligned(PRectangle rc, Fill fill) { - FillRectangle(PixelAlign(rc, PixelDivisions()), fill); -} - -void SurfaceD2D::FillRectangle(PRectangle rc, Surface &surfacePattern) { - SurfaceD2D *psurfOther = dynamic_cast<SurfaceD2D *>(&surfacePattern); - PLATFORM_ASSERT(psurfOther); - if (!psurfOther) { - throw std::runtime_error("SurfaceD2D::FillRectangle: wrong Surface type."); - } - ComPtr<ID2D1Bitmap> pBitmap; - HRESULT hr = psurfOther->GetBitmap(pBitmap.GetAddressOf()); - if (SUCCEEDED(hr) && pBitmap) { - ComPtr<ID2D1BitmapBrush> pBitmapBrush; - const D2D1_BITMAP_BRUSH_PROPERTIES brushProperties = - D2D1::BitmapBrushProperties(D2D1_EXTEND_MODE_WRAP, D2D1_EXTEND_MODE_WRAP, - D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR); - // Create the bitmap brush. - hr = pRenderTarget->CreateBitmapBrush(pBitmap.Get(), brushProperties, pBitmapBrush.GetAddressOf()); - if (SUCCEEDED(hr) && pBitmapBrush) { - pRenderTarget->FillRectangle( - RectangleFromPRectangle(rc), - pBitmapBrush.Get()); - } - } -} - -void SurfaceD2D::RoundedRectangle(PRectangle rc, FillStroke fillStroke) { - if (pRenderTarget) { - const FLOAT minDimension = static_cast<FLOAT>(std::min(rc.Width(), rc.Height())) / 2.0f; - const FLOAT radius = std::min(4.0f, minDimension); - if (fillStroke.fill.colour == fillStroke.stroke.colour) { - const D2D1_ROUNDED_RECT roundedRectFill = { - RectangleFromPRectangle(rc), - radius, radius }; - D2DPenColourAlpha(fillStroke.fill.colour); - pRenderTarget->FillRoundedRectangle(roundedRectFill, pBrush.Get()); - } else { - const D2D1_ROUNDED_RECT roundedRectFill = { - RectangleFromPRectangle(rc.Inset(1.0)), - radius-1, radius-1 }; - D2DPenColourAlpha(fillStroke.fill.colour); - pRenderTarget->FillRoundedRectangle(roundedRectFill, pBrush.Get()); - - const D2D1_ROUNDED_RECT roundedRect = { - RectangleFromPRectangle(rc.Inset(0.5)), - radius, radius }; - D2DPenColourAlpha(fillStroke.stroke.colour); - pRenderTarget->DrawRoundedRectangle(roundedRect, pBrush.Get(), fillStroke.stroke.WidthF()); - } - } -} - -void SurfaceD2D::AlphaRectangle(PRectangle rc, XYPOSITION cornerSize, FillStroke fillStroke) { - const D2D1_RECT_F rect = RectangleFromPRectangle(rc); - const D2D1_RECT_F rectFill = RectangleInset(rect, fillStroke.stroke.WidthF()); - const float halfStroke = fillStroke.stroke.WidthF() / 2.0f; - const D2D1_RECT_F rectOutline = RectangleInset(rect, halfStroke); - if (pRenderTarget) { - if (cornerSize == 0) { - // When corner size is zero, draw square rectangle to prevent blurry pixels at corners - D2DPenColourAlpha(fillStroke.fill.colour); - pRenderTarget->FillRectangle(rectFill, pBrush.Get()); - - D2DPenColourAlpha(fillStroke.stroke.colour); - pRenderTarget->DrawRectangle(rectOutline, pBrush.Get(), fillStroke.stroke.WidthF()); - } else { - const float cornerSizeF = static_cast<float>(cornerSize); - const D2D1_ROUNDED_RECT roundedRectFill = { - rectFill, cornerSizeF - 1.0f, cornerSizeF - 1.0f }; - D2DPenColourAlpha(fillStroke.fill.colour); - pRenderTarget->FillRoundedRectangle(roundedRectFill, pBrush.Get()); - - const D2D1_ROUNDED_RECT roundedRect = { - rectOutline, cornerSizeF, cornerSizeF}; - D2DPenColourAlpha(fillStroke.stroke.colour); - pRenderTarget->DrawRoundedRectangle(roundedRect, pBrush.Get(), fillStroke.stroke.WidthF()); - } - } -} - -void SurfaceD2D::GradientRectangle(PRectangle rc, const std::vector<ColourStop> &stops, GradientOptions options) { - if (pRenderTarget) { - D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES lgbp { - DPointFromPoint(Point(rc.left, rc.top)), {} - }; - switch (options) { - case GradientOptions::leftToRight: - lgbp.endPoint = DPointFromPoint(Point(rc.right, rc.top)); - break; - case GradientOptions::topToBottom: - default: - lgbp.endPoint = DPointFromPoint(Point(rc.left, rc.bottom)); - break; - } - - std::vector<D2D1_GRADIENT_STOP> gradientStops; - for (const ColourStop &stop : stops) { - gradientStops.push_back({ static_cast<FLOAT>(stop.position), ColorFromColourAlpha(stop.colour) }); - } - ComPtr<ID2D1GradientStopCollection> pGradientStops; - HRESULT hr = pRenderTarget->CreateGradientStopCollection( - gradientStops.data(), static_cast<UINT32>(gradientStops.size()), pGradientStops.GetAddressOf()); - if (FAILED(hr) || !pGradientStops) { - return; - } - ComPtr<ID2D1LinearGradientBrush> pBrushLinear; - hr = pRenderTarget->CreateLinearGradientBrush( - lgbp, pGradientStops.Get(), pBrushLinear.GetAddressOf()); - if (SUCCEEDED(hr) && pBrushLinear) { - const D2D1_RECT_F rectangle = RectangleFromPRectangle(PRectangle( - std::round(rc.left), rc.top, std::round(rc.right), rc.bottom)); - pRenderTarget->FillRectangle(&rectangle, pBrushLinear.Get()); - } - } -} - -void SurfaceD2D::DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) { - if (pRenderTarget) { - 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; - - std::vector<unsigned char> image(RGBAImage::bytesPerPixel * height * width); - RGBAImage::BGRAFromRGBA(image.data(), pixelsImage, static_cast<ptrdiff_t>(height) * width); - - ComPtr<ID2D1Bitmap> bitmap; - const D2D1_SIZE_U size = D2D1::SizeU(width, height); - const D2D1_BITMAP_PROPERTIES props = {{DXGI_FORMAT_B8G8R8A8_UNORM, - D2D1_ALPHA_MODE_PREMULTIPLIED}, 72.0, 72.0}; - const HRESULT hr = pRenderTarget->CreateBitmap(size, image.data(), - width * 4, &props, bitmap.GetAddressOf()); - if (SUCCEEDED(hr)) { - const D2D1_RECT_F rcDestination = RectangleFromPRectangle(rc); - pRenderTarget->DrawBitmap(bitmap.Get(), rcDestination); - } - } -} - -void SurfaceD2D::Ellipse(PRectangle rc, FillStroke fillStroke) { - if (!pRenderTarget) - return; - const D2D1_POINT_2F centre = DPointFromPoint(rc.Centre()); - - const FLOAT radiusFill = static_cast<FLOAT>(rc.Width() / 2.0f - fillStroke.stroke.width); - const D2D1_ELLIPSE ellipseFill = { centre, radiusFill, radiusFill }; - - D2DPenColourAlpha(fillStroke.fill.colour); - pRenderTarget->FillEllipse(ellipseFill, pBrush.Get()); - - const FLOAT radiusOutline = static_cast<FLOAT>(rc.Width() / 2.0f - fillStroke.stroke.width / 2.0f); - const D2D1_ELLIPSE ellipseOutline = { centre, radiusOutline, radiusOutline }; - - D2DPenColourAlpha(fillStroke.stroke.colour); - pRenderTarget->DrawEllipse(ellipseOutline, pBrush.Get(), fillStroke.stroke.WidthF()); -} - -void SurfaceD2D::Stadium(PRectangle rc, FillStroke fillStroke, Ends ends) { - if (!pRenderTarget) - return; - if (rc.Width() < rc.Height()) { - // Can't draw nice ends so just draw a rectangle - RectangleDraw(rc, fillStroke); - return; - } - const FLOAT radius = static_cast<FLOAT>(rc.Height() / 2.0); - const FLOAT radiusFill = radius - fillStroke.stroke.WidthF(); - const FLOAT halfStroke = fillStroke.stroke.WidthF() / 2.0f; - if (ends == Surface::Ends::semiCircles) { - const D2D1_RECT_F rect = RectangleFromPRectangle(rc); - const D2D1_ROUNDED_RECT roundedRectFill = { RectangleInset(rect, fillStroke.stroke.WidthF()), - radiusFill, radiusFill }; - D2DPenColourAlpha(fillStroke.fill.colour); - pRenderTarget->FillRoundedRectangle(roundedRectFill, pBrush.Get()); - - const D2D1_ROUNDED_RECT roundedRect = { RectangleInset(rect, halfStroke), - radius, radius }; - D2DPenColourAlpha(fillStroke.stroke.colour); - pRenderTarget->DrawRoundedRectangle(roundedRect, pBrush.Get(), fillStroke.stroke.WidthF()); - } else { - const Ends leftSide = static_cast<Ends>(static_cast<int>(ends) & 0xf); - const Ends rightSide = static_cast<Ends>(static_cast<int>(ends) & 0xf0); - PRectangle rcInner = rc; - rcInner.left += radius; - rcInner.right -= radius; - const Geometry pathGeometry = GeometryCreate(); - if (!pathGeometry) - return; - if (const GeometrySink pSink = GeometrySinkCreate(pathGeometry.Get())) { - switch (leftSide) { - case Ends::leftFlat: - pSink->BeginFigure(DPointFromPoint(Point(rc.left + halfStroke, rc.top + halfStroke)), D2D1_FIGURE_BEGIN_FILLED); - pSink->AddLine(DPointFromPoint(Point(rc.left + halfStroke, rc.bottom - halfStroke))); - break; - case Ends::leftAngle: - pSink->BeginFigure(DPointFromPoint(Point(rcInner.left + halfStroke, rc.top + halfStroke)), D2D1_FIGURE_BEGIN_FILLED); - pSink->AddLine(DPointFromPoint(Point(rc.left + halfStroke, rc.Centre().y))); - pSink->AddLine(DPointFromPoint(Point(rcInner.left + halfStroke, rc.bottom - halfStroke))); - break; - case Ends::semiCircles: - default: { - pSink->BeginFigure(DPointFromPoint(Point(rcInner.left + halfStroke, rc.top + halfStroke)), D2D1_FIGURE_BEGIN_FILLED); - D2D1_ARC_SEGMENT segment{}; - segment.point = DPointFromPoint(Point(rcInner.left + halfStroke, rc.bottom - halfStroke)); - segment.size = D2D1::SizeF(radiusFill, radiusFill); - segment.rotationAngle = 0.0f; - segment.sweepDirection = D2D1_SWEEP_DIRECTION_COUNTER_CLOCKWISE; - segment.arcSize = D2D1_ARC_SIZE_SMALL; - pSink->AddArc(segment); - } - break; - } - - switch (rightSide) { - case Ends::rightFlat: - pSink->AddLine(DPointFromPoint(Point(rc.right - halfStroke, rc.bottom - halfStroke))); - pSink->AddLine(DPointFromPoint(Point(rc.right - halfStroke, rc.top + halfStroke))); - break; - case Ends::rightAngle: - pSink->AddLine(DPointFromPoint(Point(rcInner.right - halfStroke, rc.bottom - halfStroke))); - pSink->AddLine(DPointFromPoint(Point(rc.right - halfStroke, rc.Centre().y))); - pSink->AddLine(DPointFromPoint(Point(rcInner.right - halfStroke, rc.top + halfStroke))); - break; - case Ends::semiCircles: - default: { - pSink->AddLine(DPointFromPoint(Point(rcInner.right - halfStroke, rc.bottom - halfStroke))); - D2D1_ARC_SEGMENT segment{}; - segment.point = DPointFromPoint(Point(rcInner.right - halfStroke, rc.top + halfStroke)); - segment.size = D2D1::SizeF(radiusFill, radiusFill); - segment.rotationAngle = 0.0f; - segment.sweepDirection = D2D1_SWEEP_DIRECTION_COUNTER_CLOCKWISE; - segment.arcSize = D2D1_ARC_SIZE_SMALL; - pSink->AddArc(segment); - } - break; - } - - pSink->EndFigure(D2D1_FIGURE_END_CLOSED); - - pSink->Close(); - } - D2DPenColourAlpha(fillStroke.fill.colour); - pRenderTarget->FillGeometry(pathGeometry.Get(), pBrush.Get()); - D2DPenColourAlpha(fillStroke.stroke.colour); - pRenderTarget->DrawGeometry(pathGeometry.Get(), pBrush.Get(), fillStroke.stroke.WidthF()); - } -} - -void SurfaceD2D::Copy(PRectangle rc, Point from, Surface &surfaceSource) { - SurfaceD2D &surfOther = dynamic_cast<SurfaceD2D &>(surfaceSource); - ComPtr<ID2D1Bitmap> pBitmap; - const HRESULT hr = surfOther.GetBitmap(pBitmap.GetAddressOf()); - if (SUCCEEDED(hr) && pBitmap) { - const D2D1_RECT_F rcDestination = RectangleFromPRectangle(rc); - const D2D1_RECT_F rcSource = RectangleFromPRectangle(PRectangle( - from.x, from.y, from.x + rc.Width(), from.y + rc.Height())); - pRenderTarget->DrawBitmap(pBitmap.Get(), rcDestination, 1.0f, - D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR, rcSource); - } -} - -class BlobInline final : public IDWriteInlineObject { - XYPOSITION width; - - // IUnknown - STDMETHODIMP QueryInterface(REFIID riid, PVOID *ppv) override; - STDMETHODIMP_(ULONG)AddRef() override; - STDMETHODIMP_(ULONG)Release() override; - - // IDWriteInlineObject - COM_DECLSPEC_NOTHROW STDMETHODIMP Draw( - void *clientDrawingContext, - IDWriteTextRenderer *renderer, - FLOAT originX, - FLOAT originY, - BOOL isSideways, - BOOL isRightToLeft, - IUnknown *clientDrawingEffect - ) override; - COM_DECLSPEC_NOTHROW STDMETHODIMP GetMetrics(DWRITE_INLINE_OBJECT_METRICS *metrics) override; - COM_DECLSPEC_NOTHROW STDMETHODIMP GetOverhangMetrics(DWRITE_OVERHANG_METRICS *overhangs) override; - COM_DECLSPEC_NOTHROW STDMETHODIMP GetBreakConditions( - DWRITE_BREAK_CONDITION *breakConditionBefore, - DWRITE_BREAK_CONDITION *breakConditionAfter) override; -public: - explicit BlobInline(XYPOSITION width_=0.0f) noexcept : width(width_) { - } -}; - -/// Implement IUnknown -STDMETHODIMP BlobInline::QueryInterface(REFIID riid, PVOID *ppv) { - if (!ppv) - return E_POINTER; - // Never called so not checked. - *ppv = nullptr; - if (riid == IID_IUnknown) - *ppv = this; - if (riid == __uuidof(IDWriteInlineObject)) - *ppv = this; - if (!*ppv) - return E_NOINTERFACE; - return S_OK; -} - -STDMETHODIMP_(ULONG) BlobInline::AddRef() { - // Lifetime tied to Platform methods so ignore any reference operations. - return 1; -} - -STDMETHODIMP_(ULONG) BlobInline::Release() { - // Lifetime tied to Platform methods so ignore any reference operations. - return 1; -} - -/// Implement IDWriteInlineObject -COM_DECLSPEC_NOTHROW HRESULT STDMETHODCALLTYPE BlobInline::Draw( - void*, - IDWriteTextRenderer*, - FLOAT, - FLOAT, - BOOL, - BOOL, - IUnknown*) { - // Since not performing drawing, not necessary to implement - // Could be implemented by calling back into platform-independent code. - // This would allow more of the drawing to be mediated through DirectWrite. - return S_OK; -} - -COM_DECLSPEC_NOTHROW HRESULT STDMETHODCALLTYPE BlobInline::GetMetrics( - DWRITE_INLINE_OBJECT_METRICS *metrics -) { - if (!metrics) - return E_POINTER; - metrics->width = static_cast<FLOAT>(width); - metrics->height = 2; - metrics->baseline = 1; - metrics->supportsSideways = FALSE; - return S_OK; -} - -COM_DECLSPEC_NOTHROW HRESULT STDMETHODCALLTYPE BlobInline::GetOverhangMetrics( - DWRITE_OVERHANG_METRICS *overhangs -) { - if (!overhangs) - return E_POINTER; - overhangs->left = 0; - overhangs->top = 0; - overhangs->right = 0; - overhangs->bottom = 0; - return S_OK; -} - -COM_DECLSPEC_NOTHROW HRESULT STDMETHODCALLTYPE BlobInline::GetBreakConditions( - DWRITE_BREAK_CONDITION *breakConditionBefore, - DWRITE_BREAK_CONDITION *breakConditionAfter -) { - if (!breakConditionBefore || !breakConditionAfter) - return E_POINTER; - // Since not performing 2D layout, not necessary to implement - *breakConditionBefore = DWRITE_BREAK_CONDITION_NEUTRAL; - *breakConditionAfter = DWRITE_BREAK_CONDITION_NEUTRAL; - return S_OK; -} - -class ScreenLineLayout : public IScreenLineLayout { - TextLayout textLayout; - std::string text; - std::wstring buffer; - std::vector<BlobInline> blobs; - static void FillTextLayoutFormats(const IScreenLine *screenLine, IDWriteTextLayout *textLayout, std::vector<BlobInline> &blobs); - static std::wstring ReplaceRepresentation(std::string_view text); - static size_t GetPositionInLayout(std::string_view text, size_t position); -public: - explicit ScreenLineLayout(const IScreenLine *screenLine); - // Deleted so ScreenLineLayout objects can not be copied - ScreenLineLayout(const ScreenLineLayout &) = delete; - ScreenLineLayout(ScreenLineLayout &&) = delete; - ScreenLineLayout &operator=(const ScreenLineLayout &) = delete; - ScreenLineLayout &operator=(ScreenLineLayout &&) = delete; - ~ScreenLineLayout() noexcept override = default; - size_t PositionFromX(XYPOSITION xDistance, bool charPosition) override; - XYPOSITION XFromPosition(size_t caretPosition) override; - std::vector<Interval> FindRangeIntervals(size_t start, size_t end) override; -}; - -// Each char can have its own style, so we fill the textLayout with the textFormat of each char - -void ScreenLineLayout::FillTextLayoutFormats(const IScreenLine *screenLine, IDWriteTextLayout *textLayout, std::vector<BlobInline> &blobs) { - // Reserve enough entries up front so they are not moved and the pointers handed - // to textLayout remain valid. - const ptrdiff_t numRepresentations = screenLine->RepresentationCount(); - std::string_view text = screenLine->Text(); - const ptrdiff_t numTabs = std::count(std::begin(text), std::end(text), '\t'); - blobs.reserve(numRepresentations + numTabs); - - UINT32 layoutPosition = 0; - - for (size_t bytePosition = 0; bytePosition < screenLine->Length();) { - const unsigned char uch = screenLine->Text()[bytePosition]; - const unsigned int byteCount = UTF8BytesOfLead[uch]; - const UINT32 codeUnits = UTF16LengthFromUTF8ByteCount(byteCount); - const DWRITE_TEXT_RANGE textRange = { layoutPosition, codeUnits }; - - XYPOSITION representationWidth = screenLine->RepresentationWidth(bytePosition); - if ((representationWidth == 0.0f) && (screenLine->Text()[bytePosition] == '\t')) { - D2D1_POINT_2F realPt {}; - DWRITE_HIT_TEST_METRICS realCaretMetrics {}; - textLayout->HitTestTextPosition( - layoutPosition, - false, // trailing if false, else leading edge - &realPt.x, - &realPt.y, - &realCaretMetrics - ); - - const XYPOSITION nextTab = screenLine->TabPositionAfter(realPt.x); - representationWidth = nextTab - realPt.x; - } - if (representationWidth > 0.0f) { - blobs.push_back(BlobInline(representationWidth)); - textLayout->SetInlineObject(&blobs.back(), textRange); - }; - - const FontDirectWrite *pfm = - dynamic_cast<const FontDirectWrite *>(screenLine->FontOfPosition(bytePosition)); - if (!pfm) { - throw std::runtime_error("FillTextLayoutFormats: wrong Font type."); - } - - const unsigned int fontFamilyNameSize = pfm->pTextFormat->GetFontFamilyNameLength(); - std::wstring fontFamilyName(fontFamilyNameSize, 0); - const HRESULT hrFamily = pfm->pTextFormat->GetFontFamilyName(fontFamilyName.data(), fontFamilyNameSize + 1); - if (SUCCEEDED(hrFamily)) { - textLayout->SetFontFamilyName(fontFamilyName.c_str(), textRange); - } - - textLayout->SetFontSize(pfm->pTextFormat->GetFontSize(), textRange); - textLayout->SetFontWeight(pfm->pTextFormat->GetFontWeight(), textRange); - textLayout->SetFontStyle(pfm->pTextFormat->GetFontStyle(), textRange); - - const unsigned int localeNameSize = pfm->pTextFormat->GetLocaleNameLength(); - std::wstring localeName(localeNameSize, 0); - const HRESULT hrLocale = pfm->pTextFormat->GetLocaleName(localeName.data(), localeNameSize + 1); - if (SUCCEEDED(hrLocale)) { - textLayout->SetLocaleName(localeName.c_str(), textRange); - } - - textLayout->SetFontStretch(pfm->pTextFormat->GetFontStretch(), textRange); - - IDWriteFontCollection *fontCollection = nullptr; - if (SUCCEEDED(pfm->pTextFormat->GetFontCollection(&fontCollection))) { - textLayout->SetFontCollection(fontCollection, textRange); - } - - bytePosition += byteCount; - layoutPosition += codeUnits; - } - -} - -/* Convert to a wide character string and replace tabs with X to stop DirectWrite tab expansion */ - -std::wstring ScreenLineLayout::ReplaceRepresentation(std::string_view text) { - const TextWide wideText(text, CpUtf8); - std::wstring ws(wideText.buffer, wideText.tlen); - std::replace(ws.begin(), ws.end(), L'\t', L'X'); - return ws; -} - -// Finds the position in the wide character version of the text. - -size_t ScreenLineLayout::GetPositionInLayout(std::string_view text, size_t position) { - const std::string_view textUptoPosition = text.substr(0, position); - return UTF16Length(textUptoPosition); -} - -ScreenLineLayout::ScreenLineLayout(const IScreenLine *screenLine) { - // If the text is empty, then no need to go through this function - if (!screenLine || !screenLine->Length()) - return; - - text = screenLine->Text(); - - // Get textFormat - const FontDirectWrite *pfm = FontDirectWrite::Cast(screenLine->FontOfPosition(0)); - if (!pfm->pTextFormat) { - return; - } - - // Convert the string to wstring and replace the original control characters with their representative chars. - buffer = ReplaceRepresentation(screenLine->Text()); - - // Create a text layout - textLayout = LayoutCreate( - buffer, - pfm->pTextFormat.Get(), - static_cast<FLOAT>(screenLine->Width()), - static_cast<FLOAT>(screenLine->Height())); - if (!textLayout) { - return; - } - - // Fill the textLayout chars with their own formats - FillTextLayoutFormats(screenLine, textLayout.Get(), blobs); -} - -// Get the position from the provided x - -size_t ScreenLineLayout::PositionFromX(XYPOSITION xDistance, bool charPosition) { - if (!textLayout) { - return 0; - } - - // Returns the text position corresponding to the mouse x,y. - // If hitting the trailing side of a cluster, return the - // leading edge of the following text position. - - BOOL isTrailingHit = FALSE; - BOOL isInside = FALSE; - DWRITE_HIT_TEST_METRICS caretMetrics {}; - - textLayout->HitTestPoint( - static_cast<FLOAT>(xDistance), - 0.0f, - &isTrailingHit, - &isInside, - &caretMetrics - ); - - DWRITE_HIT_TEST_METRICS hitTestMetrics {}; - if (isTrailingHit) { - FLOAT caretX = 0.0f; - FLOAT caretY = 0.0f; - - // Uses hit-testing to align the current caret position to a whole cluster, - // rather than residing in the middle of a base character + diacritic, - // surrogate pair, or character + UVS. - - // Align the caret to the nearest whole cluster. - textLayout->HitTestTextPosition( - caretMetrics.textPosition, - false, - &caretX, - &caretY, - &hitTestMetrics - ); - } - - size_t pos; - if (charPosition) { - pos = isTrailingHit ? hitTestMetrics.textPosition : caretMetrics.textPosition; - } else { - pos = isTrailingHit ? static_cast<size_t>(hitTestMetrics.textPosition) + hitTestMetrics.length : caretMetrics.textPosition; - } - - // Get the character position in original string - return UTF8PositionFromUTF16Position(text, pos); -} - -// Finds the point of the caret position - -XYPOSITION ScreenLineLayout::XFromPosition(size_t caretPosition) { - if (!textLayout) { - return 0.0; - } - // Convert byte positions to wchar_t positions - const size_t position = GetPositionInLayout(text, caretPosition); - - // Translate text character offset to point x,y. - DWRITE_HIT_TEST_METRICS caretMetrics {}; - D2D1_POINT_2F pt {}; - - textLayout->HitTestTextPosition( - static_cast<UINT32>(position), - false, // trailing if false, else leading edge - &pt.x, - &pt.y, - &caretMetrics - ); - - return pt.x; -} - -// Find the selection range rectangles - -std::vector<Interval> ScreenLineLayout::FindRangeIntervals(size_t start, size_t end) { - std::vector<Interval> ret; - - if (!textLayout || (start == end)) { - return ret; - } - - // Convert byte positions to wchar_t positions - const size_t startPos = GetPositionInLayout(text, start); - const size_t endPos = GetPositionInLayout(text, end); - - // Find selection range length - const size_t rangeLength = (endPos > startPos) ? (endPos - startPos) : (startPos - endPos); - - // Determine actual number of hit-test ranges - UINT32 actualHitTestCount = 0; - - // First try with 2 elements and if more needed, allocate. - std::vector<DWRITE_HIT_TEST_METRICS> hitTestMetrics(2); - textLayout->HitTestTextRange( - static_cast<UINT32>(startPos), - static_cast<UINT32>(rangeLength), - 0, // x - 0, // y - hitTestMetrics.data(), - static_cast<UINT32>(hitTestMetrics.size()), - &actualHitTestCount - ); - - if (actualHitTestCount == 0) { - return ret; - } - - if (hitTestMetrics.size() < actualHitTestCount) { - // Allocate enough room to return all hit-test metrics. - hitTestMetrics.resize(actualHitTestCount); - textLayout->HitTestTextRange( - static_cast<UINT32>(startPos), - static_cast<UINT32>(rangeLength), - 0, // x - 0, // y - hitTestMetrics.data(), - static_cast<UINT32>(hitTestMetrics.size()), - &actualHitTestCount - ); - } - - // Get the selection ranges behind the text. - - for (size_t i = 0; i < actualHitTestCount; ++i) { - // Store selection rectangle - const DWRITE_HIT_TEST_METRICS &htm = hitTestMetrics[i]; - const Interval selectionInterval { htm.left, htm.left + htm.width }; - ret.push_back(selectionInterval); - } - - return ret; -} - -std::unique_ptr<IScreenLineLayout> SurfaceD2D::Layout(const IScreenLine *screenLine) { - return std::make_unique<ScreenLineLayout>(screenLine); -} - -void SurfaceD2D::DrawTextCommon(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, int codePageOverride, UINT fuOptions) { - const FontDirectWrite *pfm = FontDirectWrite::Cast(font_); - if (pfm->pTextFormat && pRenderTarget && pBrush) { - // Use Unicode calls - const int codePageDraw = codePageOverride ? codePageOverride : pfm->CodePageText(mode.codePage); - const TextWide tbuf(text, codePageDraw); - - SetFontQuality(pfm->extraFontFlag); - if (fuOptions & ETO_CLIPPED) { - const D2D1_RECT_F rcClip = RectangleFromPRectangle(rc); - pRenderTarget->PushAxisAlignedClip(rcClip, D2D1_ANTIALIAS_MODE_ALIASED); - } - - // Explicitly creating a text layout appears a little faster - TextLayout pTextLayout = LayoutCreate( - tbuf.AsView(), - pfm->pTextFormat.Get(), - static_cast<FLOAT>(rc.Width()), - static_cast<FLOAT>(rc.Height())); - if (pTextLayout) { - const D2D1_POINT_2F origin = DPointFromPoint(Point(rc.left, ybase - pfm->yAscent)); - pRenderTarget->DrawTextLayout(origin, pTextLayout.Get(), pBrush.Get(), d2dDrawTextOptions); - } - - if (fuOptions & ETO_CLIPPED) { - pRenderTarget->PopAxisAlignedClip(); - } - } -} - -void SurfaceD2D::DrawTextNoClip(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, - ColourRGBA fore, ColourRGBA back) { - if (pRenderTarget) { - FillRectangleAligned(rc, back); - D2DPenColourAlpha(fore); - DrawTextCommon(rc, font_, ybase, text, 0, ETO_OPAQUE); - } -} - -void SurfaceD2D::DrawTextClipped(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, - ColourRGBA fore, ColourRGBA back) { - if (pRenderTarget) { - FillRectangleAligned(rc, back); - D2DPenColourAlpha(fore); - DrawTextCommon(rc, font_, ybase, text, 0, ETO_OPAQUE | ETO_CLIPPED); - } -} - -void SurfaceD2D::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 != ' ') { - if (pRenderTarget) { - D2DPenColourAlpha(fore); - DrawTextCommon(rc, font_, ybase, text, 0, 0); - } - return; - } - } -} - -namespace { - -HRESULT MeasurePositions(TextPositions &poses, const TextWide &tbuf, IDWriteTextFormat *pTextFormat) { - if (!pTextFormat) { - // Unexpected failure like no access to DirectWrite so give up. - return E_FAIL; - } - - // Initialize poses for safety. - std::fill(poses.buffer, poses.buffer + tbuf.tlen, 0.0f); - // Create a layout - TextLayout pTextLayout = LayoutCreate(tbuf.AsView(), pTextFormat); - if (!pTextLayout) { - return E_FAIL; - } - VarBuffer<DWRITE_CLUSTER_METRICS, stackBufferLength> cm(tbuf.tlen); - UINT32 count = 0; - const HRESULT hrGetCluster = pTextLayout->GetClusterMetrics(cm.buffer, tbuf.tlen, &count); - if (!SUCCEEDED(hrGetCluster)) { - return hrGetCluster; - } - const DWRITE_CLUSTER_METRICS * const clusterMetrics = cm.buffer; - // A cluster may be more than one WCHAR, such as for "ffi" which is a ligature in the Candara font - XYPOSITION position = 0.0; - int ti=0; - for (unsigned int ci=0; ci<count; ci++) { - for (unsigned int inCluster=0; inCluster<clusterMetrics[ci].length; inCluster++) { - poses.buffer[ti++] = position + clusterMetrics[ci].width * (inCluster + 1) / clusterMetrics[ci].length; - } - position += clusterMetrics[ci].width; - } - PLATFORM_ASSERT(ti == tbuf.tlen); - return S_OK; -} - -} - -void SurfaceD2D::MeasureWidths(const Font *font_, std::string_view text, XYPOSITION *positions) { - const FontDirectWrite *pfm = FontDirectWrite::Cast(font_); - const int codePageText = pfm->CodePageText(mode.codePage); - const TextWide tbuf(text, codePageText); - TextPositions poses(tbuf.tlen); - if (FAILED(MeasurePositions(poses, tbuf, pfm->pTextFormat.Get()))) { - return; - } - if (codePageText == CpUtf8) { - // Map the widths given for UTF-16 characters back onto the UTF-8 input string - size_t i = 0; - for (int ui = 0; ui < tbuf.tlen; 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<text.length()) && (ui<tbuf.tlen); bytePos++) { - positions[i++] = poses.buffer[ui]; - } - } - const XYPOSITION lastPos = (i > 0) ? positions[i - 1] : 0.0; - while (i<text.length()) { - positions[i++] = lastPos; - } - } else if (!IsDBCSCodePage(codePageText)) { - - // One char per position - PLATFORM_ASSERT(text.length() == static_cast<size_t>(tbuf.tlen)); - for (int kk=0; kk<tbuf.tlen; kk++) { - positions[kk] = poses.buffer[kk]; - } - - } else { - - // May be one or two bytes per position - int ui = 0; - for (size_t i=0; i<text.length() && ui<tbuf.tlen;) { - positions[i] = poses.buffer[ui]; - if (DBCSIsLeadByte(codePageText, text[i])) { - positions[i+1] = poses.buffer[ui]; - i += 2; - } else { - i++; - } - - ui++; - } - } -} - -XYPOSITION SurfaceD2D::WidthText(const Font *font_, std::string_view text) { - FLOAT width = 1.0; - const FontDirectWrite *pfm = FontDirectWrite::Cast(font_); - if (pfm->pTextFormat) { - const TextWide tbuf(text, pfm->CodePageText(mode.codePage)); - // Create a layout - if (TextLayout pTextLayout = LayoutCreate(tbuf.AsView(), pfm->pTextFormat.Get())) { - DWRITE_TEXT_METRICS textMetrics; - if (SUCCEEDED(pTextLayout->GetMetrics(&textMetrics))) - width = textMetrics.widthIncludingTrailingWhitespace; - } - } - return width; -} - -void SurfaceD2D::DrawTextNoClipUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, - ColourRGBA fore, ColourRGBA back) { - if (pRenderTarget) { - FillRectangleAligned(rc, back); - D2DPenColourAlpha(fore); - DrawTextCommon(rc, font_, ybase, text, CpUtf8, ETO_OPAQUE); - } -} - -void SurfaceD2D::DrawTextClippedUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, - ColourRGBA fore, ColourRGBA back) { - if (pRenderTarget) { - FillRectangleAligned(rc, back); - D2DPenColourAlpha(fore); - DrawTextCommon(rc, font_, ybase, text, CpUtf8, ETO_OPAQUE | ETO_CLIPPED); - } -} - -void SurfaceD2D::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 != ' ') { - if (pRenderTarget) { - D2DPenColourAlpha(fore); - DrawTextCommon(rc, font_, ybase, text, CpUtf8, 0); - } - return; - } - } -} - -void SurfaceD2D::MeasureWidthsUTF8(const Font *font_, std::string_view text, XYPOSITION *positions) { - const FontDirectWrite *pfm = FontDirectWrite::Cast(font_); - const TextWide tbuf(text, CpUtf8); - TextPositions poses(tbuf.tlen); - if (FAILED(MeasurePositions(poses, tbuf, pfm->pTextFormat.Get()))) { - return; - } - // Map the widths given for UTF-16 characters back onto the UTF-8 input string - size_t i = 0; - for (int ui = 0; ui < tbuf.tlen; ui++) { - const unsigned char uch = text[i]; - const unsigned int byteCount = UTF8BytesOfLead[uch]; - if (byteCount == 4) { // Non-BMP - ui++; - PLATFORM_ASSERT(ui < tbuf.tlen); - } - for (unsigned int bytePos=0; (bytePos<byteCount) && (i<text.length()) && (ui < tbuf.tlen); bytePos++) { - positions[i++] = poses.buffer[ui]; - } - } - const XYPOSITION lastPos = (i > 0) ? positions[i - 1] : 0.0; - while (i < text.length()) { - positions[i++] = lastPos; - } -} - -XYPOSITION SurfaceD2D::WidthTextUTF8(const Font * font_, std::string_view text) { - FLOAT width = 1.0; - const FontDirectWrite *pfm = FontDirectWrite::Cast(font_); - if (pfm->pTextFormat) { - const TextWide tbuf(text, CpUtf8); - // Create a layout - if (TextLayout pTextLayout = LayoutCreate(tbuf.AsView(), pfm->pTextFormat.Get())) { - DWRITE_TEXT_METRICS textMetrics; - if (SUCCEEDED(pTextLayout->GetMetrics(&textMetrics))) - width = textMetrics.widthIncludingTrailingWhitespace; - } - } - return width; -} - -XYPOSITION SurfaceD2D::Ascent(const Font *font_) { - const FontDirectWrite *pfm = FontDirectWrite::Cast(font_); - return std::ceil(pfm->yAscent); -} - -XYPOSITION SurfaceD2D::Descent(const Font *font_) { - const FontDirectWrite *pfm = FontDirectWrite::Cast(font_); - return std::ceil(pfm->yDescent); -} - -XYPOSITION SurfaceD2D::InternalLeading(const Font *font_) { - const FontDirectWrite *pfm = FontDirectWrite::Cast(font_); - return std::floor(pfm->yInternalLeading); -} - -XYPOSITION SurfaceD2D::Height(const Font *font_) { - const FontDirectWrite *pfm = FontDirectWrite::Cast(font_); - return std::ceil(pfm->yAscent) + std::ceil(pfm->yDescent); -} - -XYPOSITION SurfaceD2D::AverageCharWidth(const Font *font_) { - FLOAT width = 1.0; - const FontDirectWrite *pfm = FontDirectWrite::Cast(font_); - if (pfm->pTextFormat) { - // Create a layout - static constexpr std::wstring_view wsvAllAlpha = L"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; - if (TextLayout pTextLayout = LayoutCreate(wsvAllAlpha, pfm->pTextFormat.Get())) { - DWRITE_TEXT_METRICS textMetrics; - if (SUCCEEDED(pTextLayout->GetMetrics(&textMetrics))) - width = textMetrics.width / wsvAllAlpha.length(); - } - } - return width; -} - -void SurfaceD2D::SetClip(PRectangle rc) { - if (pRenderTarget) { - const D2D1_RECT_F rcClip = RectangleFromPRectangle(rc); - pRenderTarget->PushAxisAlignedClip(rcClip, D2D1_ANTIALIAS_MODE_ALIASED); - clipsActive++; - } -} - -void SurfaceD2D::PopClip() { - if (pRenderTarget) { - PLATFORM_ASSERT(clipsActive > 0); - pRenderTarget->PopAxisAlignedClip(); - clipsActive--; - } -} - -void SurfaceD2D::FlushCachedState() { -} - -void SurfaceD2D::FlushDrawing() { - if (pRenderTarget) { - pRenderTarget->Flush(); - } -} - -void SurfaceD2D::SetRenderingParams(std::shared_ptr<RenderingParams> renderingParams_) { - renderingParams = std::move(renderingParams_); -} - -#endif - -std::unique_ptr<Surface> Surface::Allocate([[maybe_unused]] Technology technology) { -#if defined(USE_D2D) - if (technology != Technology::Default) { - return std::make_unique<SurfaceD2D>(); - } -#endif - return std::make_unique<SurfaceGDI>(); -} - Window::~Window() noexcept = default; void Window::Destroy() noexcept { @@ -3237,927 +674,6 @@ PRectangle Window::GetMonitorRect(Point pt) { return PRectangle(); } -struct ListItemData { - const char *text; - int pixId; -}; - -class LineToItem { - std::vector<char> words; - - std::vector<ListItemData> data; - -public: - void Clear() noexcept { - words.clear(); - data.clear(); - } - - [[nodiscard]] ListItemData Get(size_t index) const noexcept { - if (index < data.size()) { - return data[index]; - } - ListItemData missing = {"", -1}; - return missing; - } - [[nodiscard]] int Count() const noexcept { - return static_cast<int>(data.size()); - } - - void AllocItem(const char *text, int pixId) { - const ListItemData lid = { text, pixId }; - data.push_back(lid); - } - - char *SetWords(const char *s) { - words = std::vector<char>(s, s+strlen(s)+1); - return words.data(); - } -}; - -const TCHAR ListBoxX_ClassName[] = TEXT("ListBoxX"); - -ListBox::ListBox() noexcept = default; - -ListBox::~ListBox() noexcept = default; - -namespace { - -ColourRGBA ColourElement(std::optional<ColourRGBA> colour, int nIndex) { - if (colour.has_value()) { - return colour.value(); - } - const DWORD colourValue = ::GetSysColor(nIndex); - return ColourRGBA(colourValue); -} - -struct LBGraphics { - GDIBitMap bm; - std::unique_ptr<Surface> pixmapLine; -#if defined(USE_D2D) - DCRenderTarget pBMDCTarget; -#endif - - void Release() noexcept { - pixmapLine.reset(); -#if defined(USE_D2D) - pBMDCTarget = nullptr; -#endif - bm.Release(); - } -}; - -} - -class ListBoxX : public ListBox { - int lineHeight = 10; - HFONT fontCopy {}; - std::unique_ptr<FontWin> fontWin; - Technology technology = Technology::Default; - RGBAImageSet images; - LineToItem lti; - HWND lb {}; - bool unicodeMode = false; - int codePage = 0; - int desiredVisibleRows = 9; - int maxItemCharacters = 0; - unsigned int aveCharWidth = 8; - Window *parent = nullptr; - int ctrlID = 0; - UINT dpi = USER_DEFAULT_SCREEN_DPI; - IListBoxDelegate *delegate = nullptr; - unsigned int maxCharWidth = 1; - WPARAM resizeHit = 0; - PRectangle rcPreSize; - Point dragOffset; - Point location; // Caret location at which the list is opened - MouseWheelDelta wheelDelta; - ListOptions options; - DWORD frameStyle = WS_THICKFRAME; - - LBGraphics graphics; - - HWND GetHWND() const noexcept; - void AppendListItem(const char *text, const char *numword); - void AdjustWindowRect(PRectangle *rc, UINT dpiAdjust) const noexcept; - int ItemHeight() const noexcept; - int MinClientWidth() const noexcept; - int TextOffset() const noexcept; - POINT GetClientExtent() const noexcept; - POINT MinTrackSize() const noexcept; - POINT MaxTrackSize() const noexcept; - void SetRedraw(bool on) noexcept; - void OnDoubleClick(); - void OnSelChange(); - void ResizeToCursor(); - void StartResize(WPARAM); - LRESULT NcHitTest(WPARAM, LPARAM) const; - void CentreItem(int n); - void AllocateBitMap(); - static LRESULT PASCAL ControlWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam); - - static constexpr POINT ItemInset {0, 0}; // Padding around whole item - static constexpr POINT TextInset {2, 0}; // Padding around text - static constexpr POINT ImageInset {1, 0}; // Padding around image - -public: - ListBoxX() = default; - ListBoxX(const ListBoxX &) = delete; - ListBoxX(ListBoxX &&) = delete; - ListBoxX &operator=(const ListBoxX &) = delete; - ListBoxX &operator=(ListBoxX &&) = delete; - ~ListBoxX() noexcept override { - if (fontCopy) { - ::DeleteObject(fontCopy); - fontCopy = {}; - } - graphics.Release(); - } - void SetFont(const Font *font) override; - void Create(Window &parent_, int ctrlID_, Point location_, int lineHeight_, bool unicodeMode_, Technology technology_) override; - void SetAverageCharWidth(int width) override; - void SetVisibleRows(int rows) override; - int GetVisibleRows() const override; - PRectangle GetDesiredRect() override; - int CaretFromEdge() override; - void Clear() noexcept override; - void Append(char *s, int type) override; - int Length() override; - void Select(int n) override; - int GetSelection() override; - int Find(const char *prefix) override; - std::string GetValue(int n) override; - void RegisterImage(int type, const char *xpm_data) override; - void RegisterRGBAImage(int type, int width, int height, const unsigned char *pixelsImage) override; - void ClearRegisteredImages() override; - void SetDelegate(IListBoxDelegate *lbDelegate) override; - void SetList(const char *list, char separator, char typesep) override; - void SetOptions(ListOptions options_) override; - void Draw(DRAWITEMSTRUCT *pDrawItem); - LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam); - static LRESULT PASCAL StaticWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam); -}; - -std::unique_ptr<ListBox> ListBox::Allocate() { - return std::make_unique<ListBoxX>(); -} - -void ListBoxX::Create(Window &parent_, int ctrlID_, Point location_, int lineHeight_, bool unicodeMode_, Technology technology_) { - parent = &parent_; - ctrlID = ctrlID_; - location = location_; - lineHeight = lineHeight_; - unicodeMode = unicodeMode_; - codePage = unicodeMode ? CpUtf8 : 0; - technology = technology_; - HWND hwndParent = HwndFromWindow(*parent); - HINSTANCE hinstanceParent = GetWindowInstance(hwndParent); - // Window created as popup so not clipped within parent client area - wid = ::CreateWindowEx( - WS_EX_WINDOWEDGE, ListBoxX_ClassName, TEXT(""), - WS_POPUP | frameStyle, - 100,100, 150,80, hwndParent, - {}, - hinstanceParent, - this); - - dpi = DpiForWindow(hwndParent); - POINT locationw = POINTFromPoint(location); - ::MapWindowPoints(hwndParent, {}, &locationw, 1); - location = PointFromPOINT(locationw); -} - -void ListBoxX::SetFont(const Font *font) { - const FontWin *pfm = dynamic_cast<const FontWin *>(font); - if (pfm) { - if (fontCopy) { - ::DeleteObject(fontCopy); - fontCopy = {}; - } - fontCopy = pfm->HFont(); - SetWindowFont(lb, fontCopy, 0); - fontWin = pfm->Duplicate(); - codePage = unicodeMode ? CpUtf8 : CodePageFromCharSet(fontWin->GetCharacterSet(), 1252); - graphics.Release(); - } -} - -void ListBoxX::SetAverageCharWidth(int width) { - aveCharWidth = width; -} - -void ListBoxX::SetVisibleRows(int rows) { - desiredVisibleRows = rows; -} - -int ListBoxX::GetVisibleRows() const { - return desiredVisibleRows; -} - -HWND ListBoxX::GetHWND() const noexcept { - return HwndFromWindowID(GetID()); -} - -PRectangle ListBoxX::GetDesiredRect() { - PRectangle rcDesired = GetPosition(); - - int rows = Length(); - if ((rows == 0) || (rows > desiredVisibleRows)) - rows = desiredVisibleRows; - rcDesired.bottom = rcDesired.top + ItemHeight() * rows; - - int width = MinClientWidth(); - int textSize = 0; - int averageCharWidth = 8; - - // Make a measuring surface - std::unique_ptr<Surface> surfaceItem(Surface::Allocate(technology)); - surfaceItem->Init(GetID()); - surfaceItem->SetMode(SurfaceMode(codePage, false)); - - // Find the widest item in pixels - const int items = lti.Count(); - for (int i = 0; i < items; i++) { - const ListItemData item = lti.Get(i); - const int itemTextSize = static_cast<int>(std::ceil( - surfaceItem->WidthText(fontWin.get(), item.text))); - textSize = std::max(textSize, itemTextSize); - } - - maxCharWidth = static_cast<int>(std::ceil(surfaceItem->WidthText(fontWin.get(), "W"))); - averageCharWidth = static_cast<int>(surfaceItem->AverageCharWidth(fontWin.get())); - - width = std::max({ width, textSize, (maxItemCharacters + 1) * averageCharWidth }); - - rcDesired.right = rcDesired.left + TextOffset() + width + (TextInset.x * 2); - if (Length() > rows) - rcDesired.right += SystemMetricsForDpi(SM_CXVSCROLL, dpi); - - AdjustWindowRect(&rcDesired, dpi); - return rcDesired; -} - -int ListBoxX::TextOffset() const noexcept { - const int pixWidth = images.GetWidth(); - return pixWidth == 0 ? ItemInset.x : ItemInset.x + pixWidth + (ImageInset.x * 2); -} - -int ListBoxX::CaretFromEdge() { - PRectangle rc; - AdjustWindowRect(&rc, dpi); - return TextOffset() + static_cast<int>(TextInset.x + (0 - rc.left) - 1); -} - -void ListBoxX::Clear() noexcept { - ListBox_ResetContent(lb); - maxItemCharacters = 0; - lti.Clear(); -} - -void ListBoxX::Append(char *, int) { - // This method is no longer called in Scintilla - PLATFORM_ASSERT(false); -} - -int ListBoxX::Length() { - return lti.Count(); -} - -void ListBoxX::Select(int n) { - // We are going to scroll to centre on the new selection and then select it, so disable - // redraw to avoid flicker caused by a painting new selection twice in unselected and then - // selected states - SetRedraw(false); - CentreItem(n); - ListBox_SetCurSel(lb, n); - OnSelChange(); - SetRedraw(true); -} - -int ListBoxX::GetSelection() { - return ListBox_GetCurSel(lb); -} - -// This is not actually called at present -int ListBoxX::Find(const char *) { - return LB_ERR; -} - -std::string ListBoxX::GetValue(int n) { - const ListItemData item = lti.Get(n); - return item.text; -} - -void ListBoxX::RegisterImage(int type, const char *xpm_data) { - XPM xpmImage(xpm_data); - images.AddImage(type, std::make_unique<RGBAImage>(xpmImage)); -} - -void ListBoxX::RegisterRGBAImage(int type, int width, int height, const unsigned char *pixelsImage) { - images.AddImage(type, std::make_unique<RGBAImage>(width, height, 1.0f, pixelsImage)); -} - -void ListBoxX::ClearRegisteredImages() { - images.Clear(); -} - -void ListBoxX::Draw(DRAWITEMSTRUCT *pDrawItem) { - if ((pDrawItem->itemAction != ODA_SELECT) && (pDrawItem->itemAction != ODA_DRAWENTIRE)) { - return; - } - if (!graphics.pixmapLine) { - AllocateBitMap(); - if (!graphics.pixmapLine) { - // Failed to allocate, so release fully and give up - graphics.Release(); - return; - } - } -#if defined(USE_D2D) - if (graphics.pBMDCTarget) { - graphics.pBMDCTarget->BeginDraw(); - } -#endif - - const PRectangle rcItemBase = PRectangleFromRECT(pDrawItem->rcItem); - const PRectangle rcItem(0, 0, rcItemBase.Width(), rcItemBase.Height()); - PRectangle rcBox = rcItem; - rcBox.left += TextOffset(); - ColourRGBA colourFore; - ColourRGBA colourBack; - if (pDrawItem->itemState & ODS_SELECTED) { - PRectangle rcImage = rcItem; - rcImage.right = rcBox.left; - // The image is not highlighted - graphics.pixmapLine->FillRectangle(rcImage, ColourElement(options.back, COLOR_WINDOW)); - colourBack = ColourElement(options.backSelected, COLOR_HIGHLIGHT); - graphics.pixmapLine->FillRectangle(rcBox, colourBack); - colourFore = ColourElement(options.foreSelected, COLOR_HIGHLIGHTTEXT); - } else { - colourBack = ColourElement(options.back, COLOR_WINDOW); - graphics.pixmapLine->FillRectangle(rcItem, colourBack); - colourFore = ColourElement(options.fore, COLOR_WINDOWTEXT); - } - - const ListItemData item = lti.Get(pDrawItem->itemID); - const int pixId = item.pixId; - const char *text = item.text; - - const PRectangle rcText = rcBox.Inset(Point(TextInset.x, TextInset.y)); - - const double ascent = graphics.pixmapLine->Ascent(fontWin.get()); - graphics.pixmapLine->DrawTextClipped(rcText, fontWin.get(), rcText.top + ascent, text, colourFore, colourBack); - - // Draw the image, if any - const RGBAImage *pimage = images.Get(pixId); - if (pimage) { - const XYPOSITION left = rcItem.left + ItemInset.x + ImageInset.x; - PRectangle rcImage = rcItem; - rcImage.left = left; - rcImage.right = rcImage.left + images.GetWidth(); - graphics.pixmapLine->DrawRGBAImage(rcImage, - pimage->GetWidth(), pimage->GetHeight(), pimage->Pixels()); - } - -#if defined(USE_D2D) - if (graphics.pBMDCTarget) { - const HRESULT hrEnd = graphics.pBMDCTarget->EndDraw(); - if (FAILED(hrEnd)) { - return; - } - } -#endif - - // Blit from hMemDC to hDC - const SIZE extent = SizeOfRect(pDrawItem->rcItem); - ::BitBlt(pDrawItem->hDC, pDrawItem->rcItem.left, pDrawItem->rcItem.top, extent.cx, extent.cy, graphics.bm.DC(), 0, 0, SRCCOPY); -} - -void ListBoxX::AppendListItem(const char *text, const char *numword) { - int pixId = -1; - if (numword) { - pixId = 0; - char ch; - while ((ch = *++numword) != '\0') { - pixId = 10 * pixId + (ch - '0'); - } - } - - lti.AllocItem(text, pixId); - maxItemCharacters = std::max(maxItemCharacters, static_cast<int>(strlen(text))); -} - -void ListBoxX::SetDelegate(IListBoxDelegate *lbDelegate) { - delegate = lbDelegate; -} - -void ListBoxX::SetList(const char *list, char separator, char typesep) { - // Turn off redraw while populating the list - this has a significant effect, even if - // the listbox is not visible. - SetRedraw(false); - Clear(); - const size_t size = strlen(list); - char *words = lti.SetWords(list); - const char *startword = words; - char *numword = nullptr; - for (size_t i=0; i < size; i++) { - if (words[i] == separator) { - words[i] = '\0'; - if (numword) - *numword = '\0'; - AppendListItem(startword, numword); - startword = words + i + 1; - numword = nullptr; - } else if (words[i] == typesep) { - numword = words + i; - } - } - if (startword) { - if (numword) - *numword = '\0'; - AppendListItem(startword, numword); - } - - // Finally populate the listbox itself with the correct number of items - const int count = lti.Count(); - ::SendMessage(lb, LB_INITSTORAGE, count, 0); - for (intptr_t j=0; j<count; j++) { - ListBox_AddItemData(lb, j+1); - } - SetRedraw(true); -} - -void ListBoxX::SetOptions(ListOptions options_) { - options = options_; - frameStyle = FlagSet(options.options, AutoCompleteOption::FixedSize) ? WS_BORDER : WS_THICKFRAME; -} - -void ListBoxX::AdjustWindowRect(PRectangle *rc, UINT dpiAdjust) const noexcept { - RECT rcw = RectFromPRectangle(*rc); - if (fnAdjustWindowRectExForDpi) { - fnAdjustWindowRectExForDpi(&rcw, frameStyle, false, WS_EX_WINDOWEDGE, dpiAdjust); - } else { - ::AdjustWindowRectEx(&rcw, frameStyle, false, WS_EX_WINDOWEDGE); - } - *rc = PRectangleFromRECT(rcw); -} - -int ListBoxX::ItemHeight() const noexcept { - int itemHeight = lineHeight + (TextInset.y * 2); - const int pixHeight = images.GetHeight() + (ImageInset.y * 2); - if (itemHeight < pixHeight) { - itemHeight = pixHeight; - } - return itemHeight; -} - -int ListBoxX::MinClientWidth() const noexcept { - return 12 * (aveCharWidth+aveCharWidth/3); -} - -POINT ListBoxX::MinTrackSize() const noexcept { - PRectangle rc = PRectangle::FromInts(0, 0, MinClientWidth(), ItemHeight()); - AdjustWindowRect(&rc, dpi); - POINT ret = {static_cast<LONG>(rc.Width()), static_cast<LONG>(rc.Height())}; - return ret; -} - -POINT ListBoxX::MaxTrackSize() const noexcept { - PRectangle rc = PRectangle::FromInts(0, 0, - std::max<int>(static_cast<unsigned int>(MinClientWidth()), - maxCharWidth * maxItemCharacters + TextInset.x * 2 + - TextOffset() + SystemMetricsForDpi(SM_CXVSCROLL, dpi)), - ItemHeight() * lti.Count()); - AdjustWindowRect(&rc, dpi); - POINT ret = {static_cast<LONG>(rc.Width()), static_cast<LONG>(rc.Height())}; - return ret; -} - -void ListBoxX::SetRedraw(bool on) noexcept { - ::SendMessage(lb, WM_SETREDRAW, on, 0); - if (on) { - ::RedrawWindow(lb, {}, {}, RDW_ERASE | RDW_FRAME | RDW_INVALIDATE | RDW_ALLCHILDREN); - } -} - -void ListBoxX::ResizeToCursor() { - PRectangle rc = GetPosition(); - POINT ptw; - ::GetCursorPos(&ptw); - const Point pt = PointFromPOINT(ptw) + dragOffset; - - switch (resizeHit) { - case HTLEFT: - rc.left = pt.x; - break; - case HTRIGHT: - rc.right = pt.x; - break; - case HTTOP: - rc.top = pt.y; - break; - case HTTOPLEFT: - rc.top = pt.y; - rc.left = pt.x; - break; - case HTTOPRIGHT: - rc.top = pt.y; - rc.right = pt.x; - break; - case HTBOTTOM: - rc.bottom = pt.y; - break; - case HTBOTTOMLEFT: - rc.bottom = pt.y; - rc.left = pt.x; - break; - case HTBOTTOMRIGHT: - rc.bottom = pt.y; - rc.right = pt.x; - break; - default: - break; - } - - const POINT ptMin = MinTrackSize(); - const POINT ptMax = MaxTrackSize(); - // We don't allow the left edge to move at present, but just in case - rc.left = std::clamp(rc.left, rcPreSize.right - ptMax.x, rcPreSize.right - ptMin.x); - rc.top = std::clamp(rc.top, rcPreSize.bottom - ptMax.y, rcPreSize.bottom - ptMin.y); - rc.right = std::clamp(rc.right, rcPreSize.left + ptMin.x, rcPreSize.left + ptMax.x); - rc.bottom = std::clamp(rc.bottom, rcPreSize.top + ptMin.y, rcPreSize.top + ptMax.y); - - SetPosition(rc); -} - -void ListBoxX::StartResize(WPARAM hitCode) { - rcPreSize = GetPosition(); - POINT cursorPos; - ::GetCursorPos(&cursorPos); - - switch (hitCode) { - case HTRIGHT: - case HTBOTTOM: - case HTBOTTOMRIGHT: - dragOffset.x = rcPreSize.right - cursorPos.x; - dragOffset.y = rcPreSize.bottom - cursorPos.y; - break; - - case HTTOPRIGHT: - dragOffset.x = rcPreSize.right - cursorPos.x; - dragOffset.y = rcPreSize.top - cursorPos.y; - break; - - // Note that the current hit test code prevents the left edge cases ever firing - // as we don't want the left edge to be movable - case HTLEFT: - case HTTOP: - case HTTOPLEFT: - dragOffset.x = rcPreSize.left - cursorPos.x; - dragOffset.y = rcPreSize.top - cursorPos.y; - break; - case HTBOTTOMLEFT: - dragOffset.x = rcPreSize.left - cursorPos.x; - dragOffset.y = rcPreSize.bottom - cursorPos.y; - break; - - default: - return; - } - - ::SetCapture(GetHWND()); - resizeHit = hitCode; -} - -LRESULT ListBoxX::NcHitTest(WPARAM wParam, LPARAM lParam) const { - const PRectangle rc = GetPosition(); - - LRESULT hit = ::DefWindowProc(GetHWND(), WM_NCHITTEST, wParam, lParam); - // There is an apparent bug in the DefWindowProc hit test code whereby it will - // return HTTOPXXX if the window in question is shorter than the default - // window caption height + frame, even if one is hovering over the bottom edge of - // the frame, so workaround that here - if (hit >= HTTOP && hit <= HTTOPRIGHT) { - const int minHeight = SystemMetricsForDpi(SM_CYMINTRACK, dpi); - const int yPos = GET_Y_LPARAM(lParam); - if ((rc.Height() < minHeight) && (yPos > ((rc.top + rc.bottom)/2))) { - hit += HTBOTTOM - HTTOP; - } - } - - // Never permit resizing that moves the left edge. Allow movement of top or bottom edge - // depending on whether the list is above or below the caret - switch (hit) { - case HTLEFT: - case HTTOPLEFT: - case HTBOTTOMLEFT: - hit = HTERROR; - break; - - case HTTOP: - case HTTOPRIGHT: { - // Valid only if caret below list - if (location.y < rc.top) - hit = HTERROR; - } - break; - - case HTBOTTOM: - case HTBOTTOMRIGHT: { - // Valid only if caret above list - if (rc.bottom <= location.y) - hit = HTERROR; - } - break; - default: - break; - } - - return hit; -} - -void ListBoxX::OnDoubleClick() { - if (delegate) { - ListBoxEvent event(ListBoxEvent::EventType::doubleClick); - delegate->ListNotify(&event); - } -} - -void ListBoxX::OnSelChange() { - if (delegate) { - ListBoxEvent event(ListBoxEvent::EventType::selectionChange); - delegate->ListNotify(&event); - } -} - -POINT ListBoxX::GetClientExtent() const noexcept { - RECT rc; - ::GetWindowRect(HwndFromWindowID(wid), &rc); - POINT ret { rc.right - rc.left, rc.bottom - rc.top }; - return ret; -} - -void ListBoxX::CentreItem(int n) { - // If below mid point, scroll up to centre, but with more items below if uneven - if (n >= 0) { - const POINT extent = GetClientExtent(); - const int visible = extent.y/ItemHeight(); - if (visible < Length()) { - const int top = ListBox_GetTopIndex(lb); - const int half = (visible - 1) / 2; - if (n > (top + half)) - ListBox_SetTopIndex(lb, n - half); - } - } -} - -void ListBoxX::AllocateBitMap() { - const SIZE extent { GetClientExtent().x, lineHeight }; - - graphics.bm.Create({}, extent.cx, -extent.cy, nullptr); - if (!graphics.bm) { - return; - } - - // Make a surface - graphics.pixmapLine = Surface::Allocate(technology); - graphics.pixmapLine->SetMode(SurfaceMode(codePage, false)); - -#if defined(USE_D2D) - if (technology != Technology::Default) { - if (!LoadD2D()) { - return; - } - - const D2D1_RENDER_TARGET_PROPERTIES drtp = D2D1::RenderTargetProperties( - D2D1_RENDER_TARGET_TYPE_DEFAULT, - { DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED }); - - HRESULT hr = CreateDCRenderTarget(&drtp, graphics.pBMDCTarget); - if (FAILED(hr) || !graphics.pBMDCTarget) { - return; - } - - const RECT rcExtent = { 0, 0, extent.cx, extent.cy }; - hr = graphics.pBMDCTarget->BindDC(graphics.bm.DC(), &rcExtent); - if (SUCCEEDED(hr)) { - graphics.pixmapLine->Init(graphics.pBMDCTarget.Get(), GetID()); - } - return; - } -#endif - - // Either technology == Technology::Default or USE_D2D turned off - graphics.pixmapLine->Init(graphics.bm.DC(), GetID()); -} - -LRESULT PASCAL ListBoxX::ControlWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) { - try { - ListBoxX *lbx = static_cast<ListBoxX *>(PointerFromWindow(::GetParent(hWnd))); - switch (iMessage) { - case WM_ERASEBKGND: - return TRUE; - - case WM_MOUSEACTIVATE: - // This prevents the view activating when the scrollbar is clicked - return MA_NOACTIVATE; - - case WM_LBUTTONDOWN: { - // We must take control of selection to prevent the ListBox activating - // the popup - const LRESULT lResult = ::SendMessage(hWnd, LB_ITEMFROMPOINT, 0, lParam); - if (HIWORD(lResult) == 0) { - ListBox_SetCurSel(hWnd, LOWORD(lResult)); - if (lbx) { - lbx->OnSelChange(); - } - } - } - return 0; - - case WM_LBUTTONUP: - return 0; - - case WM_LBUTTONDBLCLK: { - if (lbx) { - lbx->OnDoubleClick(); - } - } - return 0; - - case WM_MBUTTONDOWN: - // disable the scroll wheel button click action - return 0; - - default: - break; - } - - WNDPROC prevWndProc = reinterpret_cast<WNDPROC>(GetWindowLongPtr(hWnd, GWLP_USERDATA)); - if (prevWndProc) { - return ::CallWindowProc(prevWndProc, hWnd, iMessage, wParam, lParam); - } - return ::DefWindowProc(hWnd, iMessage, wParam, lParam); - } catch (...) { - } - return ::DefWindowProc(hWnd, iMessage, wParam, lParam); -} - -LRESULT ListBoxX::WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) { - switch (iMessage) { - case WM_CREATE: { - HINSTANCE hinstanceParent = GetWindowInstance(HwndFromWindow(*parent)); - // Note that LBS_NOINTEGRALHEIGHT is specified to fix cosmetic issue when resizing the list - // but has useful side effect of speeding up list population significantly - lb = ::CreateWindowEx( - 0, TEXT("listbox"), TEXT(""), - WS_CHILD | WS_VSCROLL | WS_VISIBLE | - LBS_OWNERDRAWFIXED | LBS_NODATA | LBS_NOINTEGRALHEIGHT, - 0, 0, 150,80, hWnd, - reinterpret_cast<HMENU>(static_cast<ptrdiff_t>(ctrlID)), - hinstanceParent, - nullptr); - WNDPROC prevWndProc = SubclassWindow(lb, ControlWndProc); - ::SetWindowLongPtr(lb, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(prevWndProc)); - } - break; - - case WM_SIZE: - if (lb) { - graphics.Release(); // Bitmap must be reallocated to new size. - SetRedraw(false); - ::SetWindowPos(lb, {}, 0, 0, LOWORD(lParam), HIWORD(lParam), SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE); - // Ensure the selection remains visible - CentreItem(GetSelection()); - SetRedraw(true); - } - break; - - case WM_PAINT: { - Painter painter(hWnd); - } - break; - - case WM_COMMAND: - // This is not actually needed now - the registered double click action is used - // directly to action a choice from the list. - ::SendMessage(HwndFromWindow(*parent), iMessage, wParam, lParam); - break; - - case WM_MEASUREITEM: { - MEASUREITEMSTRUCT *pMeasureItem = reinterpret_cast<MEASUREITEMSTRUCT *>(lParam); - pMeasureItem->itemHeight = ItemHeight(); - } - break; - - case WM_DRAWITEM: - Draw(reinterpret_cast<DRAWITEMSTRUCT *>(lParam)); - break; - - case WM_DESTROY: - lb = {}; - SetWindowPointer(hWnd, nullptr); - return ::DefWindowProc(hWnd, iMessage, wParam, lParam); - - case WM_ERASEBKGND: - // To reduce flicker we can elide background erasure since this window is - // completely covered by its child. - return TRUE; - - case WM_GETMINMAXINFO: { - MINMAXINFO *minMax = reinterpret_cast<MINMAXINFO*>(lParam); - minMax->ptMaxTrackSize = MaxTrackSize(); - minMax->ptMinTrackSize = MinTrackSize(); - } - break; - - case WM_MOUSEACTIVATE: - return MA_NOACTIVATE; - - case WM_NCHITTEST: - return NcHitTest(wParam, lParam); - - case WM_NCLBUTTONDOWN: - // We have to implement our own window resizing because the DefWindowProc - // implementation insists on activating the resized window - StartResize(wParam); - return 0; - - case WM_MOUSEMOVE: { - if (resizeHit == 0) { - return ::DefWindowProc(hWnd, iMessage, wParam, lParam); - } - ResizeToCursor(); - } - break; - - case WM_LBUTTONUP: - case WM_CANCELMODE: - if (resizeHit != 0) { - resizeHit = 0; - ::ReleaseCapture(); - } - return ::DefWindowProc(hWnd, iMessage, wParam, lParam); - case WM_MOUSEWHEEL: - if (wheelDelta.Accumulate(wParam)) { - const int nRows = GetVisibleRows(); - int linesToScroll = std::clamp(nRows - 1, 1, 3); - linesToScroll *= wheelDelta.Actions(); - int top = ListBox_GetTopIndex(lb) + linesToScroll; - if (top < 0) { - top = 0; - } - ListBox_SetTopIndex(lb, top); - } - break; - - default: - return ::DefWindowProc(hWnd, iMessage, wParam, lParam); - } - - return 0; -} - -LRESULT PASCAL ListBoxX::StaticWndProc( - HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) { - if (iMessage == WM_CREATE) { - CREATESTRUCT *pCreate = reinterpret_cast<CREATESTRUCT *>(lParam); - SetWindowPointer(hWnd, pCreate->lpCreateParams); - } - // Find C++ object associated with window. - ListBoxX *lbx = static_cast<ListBoxX *>(PointerFromWindow(hWnd)); - if (lbx) { - return lbx->WndProc(hWnd, iMessage, wParam, lParam); - } - return ::DefWindowProc(hWnd, iMessage, wParam, lParam); -} - -namespace { - -bool ListBoxX_Register() noexcept { - WNDCLASSEX wndclassc {}; - wndclassc.cbSize = sizeof(wndclassc); - // We need CS_HREDRAW and CS_VREDRAW because of the ellipsis that might be drawn for - // truncated items in the list and the appearance/disappearance of the vertical scroll bar. - // The list repaint is double-buffered to avoid the flicker this would otherwise cause. - wndclassc.style = CS_GLOBALCLASS | CS_HREDRAW | CS_VREDRAW; - wndclassc.cbWndExtra = sizeof(ListBoxX *); - wndclassc.hInstance = hinstPlatformRes; - wndclassc.lpfnWndProc = ListBoxX::StaticWndProc; - wndclassc.hCursor = ::LoadCursor({}, IDC_ARROW); - wndclassc.lpszClassName = ListBoxX_ClassName; - - return ::RegisterClassEx(&wndclassc) != 0; -} - -void ListBoxX_Unregister() noexcept { - if (hinstPlatformRes) { - ::UnregisterClass(ListBoxX_ClassName, hinstPlatformRes); - } -} - -} - Menu::Menu() noexcept : mid{} { } @@ -4221,13 +737,6 @@ void Platform::DebugPrintf(const char *, ...) noexcept { namespace { -void ReleaseLibrary(HMODULE &hLib) noexcept { - if (hLib) { - FreeLibrary(hLib); - hLib = {}; - } -} - bool assertionPopUps = true; } @@ -4267,11 +776,7 @@ void Platform_Initialise(void *hInstance) noexcept { void Platform_Finalise(bool fromDllMain) noexcept { if (!fromDllMain) { #if defined(USE_D2D) - ReleaseUnknown(pIDWriteFactory); - ReleaseUnknown(pD2DFactory); - ReleaseLibrary(hDLLDWrite); - ReleaseLibrary(hDLLD3D); - ReleaseLibrary(hDLLD2D); + ReleaseD2D(); #endif ReleaseLibrary(hDLLShcore); } diff --git a/win32/PlatWin.h b/win32/PlatWin.h index dcda7c944..27f9b3e3d 100644 --- a/win32/PlatWin.h +++ b/win32/PlatWin.h @@ -49,6 +49,10 @@ inline HWND HwndFromWindow(const Window &w) noexcept { return HwndFromWindowID(w.GetID()); } +extern HINSTANCE hinstPlatformRes; + +UINT CodePageFromCharSet(CharacterSet characterSet, UINT documentCodePage) noexcept; + void *PointerFromWindow(HWND hWnd) noexcept; void SetWindowPointer(HWND hWnd, void *ptr) noexcept; @@ -59,6 +63,8 @@ float GetDeviceScaleFactorWhenGdiScalingActive(HWND hWnd) noexcept; int SystemMetricsForDpi(int nIndex, UINT dpi) noexcept; +void AdjustWindowRectForDpi(LPRECT lpRect, DWORD dwStyle, UINT dpi) noexcept; + HCURSOR LoadReverseArrowCursor(UINT dpi) noexcept; // Encapsulate WM_PAINT handling so that EndPaint is always called even with unexpected returns or exceptions. @@ -87,29 +93,89 @@ public: } }; -#if defined(USE_D2D) -extern bool LoadD2D() noexcept; -extern ID2D1Factory1 *pD2DFactory; -extern IDWriteFactory1 *pIDWriteFactory; - -using DCRenderTarget = ComPtr<ID2D1DCRenderTarget>; - -using D3D11Device = ComPtr<ID3D11Device1>; - -HRESULT CreateDCRenderTarget(const D2D1_RENDER_TARGET_PROPERTIES *renderTargetProperties, DCRenderTarget &dcRT) noexcept; -extern HRESULT CreateD3D(D3D11Device &device) noexcept; +// Both GDI and DirectWrite can produce a HFONT for use in list boxes +struct FontWin : public Font { + [[nodiscard]] virtual HFONT HFont() const noexcept = 0; + [[nodiscard]] virtual std::unique_ptr<FontWin> Duplicate() const = 0; + [[nodiscard]] virtual CharacterSet GetCharacterSet() const noexcept = 0; +}; -using WriteRenderingParams = ComPtr<IDWriteRenderingParams1>; +// Buffer to hold strings and string position arrays without always allocating on heap. +// May sometimes have string too long to allocate on stack. So use a fixed stack-allocated buffer +// when less than safe size otherwise allocate on heap and free automatically. +template<typename T, int lengthStandard> +class VarBuffer { + T bufferStandard[lengthStandard]; +public: + T *buffer; + explicit VarBuffer(size_t length) : buffer(nullptr) { + if (length > lengthStandard) { + buffer = new T[length]; + } else { + buffer = bufferStandard; + } + } + // Deleted so VarBuffer objects can not be copied. + VarBuffer(const VarBuffer &) = delete; + VarBuffer(VarBuffer &&) = delete; + VarBuffer &operator=(const VarBuffer &) = delete; + VarBuffer &operator=(VarBuffer &&) = delete; + + ~VarBuffer() noexcept { + if (buffer != bufferStandard) { + delete[]buffer; + buffer = nullptr; + } + } +}; -struct RenderingParams { - WriteRenderingParams defaultRenderingParams; - WriteRenderingParams customRenderingParams; +constexpr int stackBufferLength = 400; +class TextWide : public VarBuffer<wchar_t, stackBufferLength> { +public: + int tlen; // Using int instead of size_t as most Win32 APIs take int. + TextWide(std::string_view text, int codePage) : + VarBuffer<wchar_t, stackBufferLength>(text.length()) { + if (codePage == CpUtf8) { + tlen = static_cast<int>(UTF16FromUTF8(text, buffer, text.length())); + } else { + // Support Asian string display in 9x English + tlen = ::MultiByteToWideChar(codePage, 0, text.data(), static_cast<int>(text.length()), + buffer, static_cast<int>(text.length())); + } + } + [[nodiscard]] std::wstring_view AsView() const noexcept { + return std::wstring_view(buffer, tlen); + } }; +using TextPositions = VarBuffer<XYPOSITION, stackBufferLength>; + +// Manage the lifetime of a memory HBITMAP and its HDC so there are no leaks. +class GDIBitMap { + HDC hdc{}; + HBITMAP hbm{}; + HBITMAP hbmOriginal{}; -struct ISetRenderingParams { - virtual void SetRenderingParams(std::shared_ptr<RenderingParams> renderingParams_) = 0; +public: + GDIBitMap() noexcept = default; + // Deleted so GDIBitMap objects can not be copied. + GDIBitMap(const GDIBitMap &) = delete; + GDIBitMap(GDIBitMap &&) = delete; + // Move would be OK but not needed yet + GDIBitMap &operator=(const GDIBitMap &) = delete; + GDIBitMap &operator=(GDIBitMap &&) = delete; + ~GDIBitMap() noexcept; + + void Create(HDC hdcBase, int width, int height, DWORD **pixels) noexcept; + void Release() noexcept; + HBITMAP Extract() noexcept; + + [[nodiscard]] HDC DC() const noexcept { + return hdc; + } + [[nodiscard]] explicit operator bool() const noexcept { + return hdc && hbm; + } }; -#endif } diff --git a/win32/Scintilla.vcxproj b/win32/Scintilla.vcxproj index 3a416b053..0336654a9 100644 --- a/win32/Scintilla.vcxproj +++ b/win32/Scintilla.vcxproj @@ -169,6 +169,9 @@ <ClCompile Include="..\src\*.cxx" />
<ClCompile Include="..\win32\HanjaDic.cxx" />
<ClCompile Include="..\win32\PlatWin.cxx" />
+ <ClCompile Include="..\win32\ListBox.cxx" />
+ <ClCompile Include="..\win32\SurfaceGDI.cxx" />
+ <ClCompile Include="..\win32\SurfaceD2D.cxx" />
<ClCompile Include="..\win32\ScintillaWin.cxx" />
<ClCompile Include="..\win32\ScintillaDLL.cxx" />
</ItemGroup>
diff --git a/win32/ScintillaWin.cxx b/win32/ScintillaWin.cxx index 7cd8ff086..c40119169 100644 --- a/win32/ScintillaWin.cxx +++ b/win32/ScintillaWin.cxx @@ -99,6 +99,9 @@ using Microsoft::WRL::ComPtr; #include "WinTypes.h" #include "PlatWin.h" +#if defined(USE_D2D) +#include "SurfaceD2D.h" +#endif #include "HanjaDic.h" #include "ScintillaWin.h" diff --git a/win32/SurfaceD2D.cxx b/win32/SurfaceD2D.cxx new file mode 100644 index 000000000..5ebf2191a --- /dev/null +++ b/win32/SurfaceD2D.cxx @@ -0,0 +1,1738 @@ +// Scintilla source code edit control +/** @file SurfaceD2D.cxx + ** Implementation of drawing to Direct2D 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 <wrl.h> +using Microsoft::WRL::ComPtr; + +#if !defined(DISABLE_D2D) +#define USE_D2D 1 +#endif + +#if defined(USE_D2D) +#include <d2d1_1.h> +#include <d3d11_1.h> +#include <dwrite_1.h> +#endif + +#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" +#if defined(USE_D2D) +#include "SurfaceD2D.h" +#endif + +// __uuidof is a Microsoft extension but makes COM code neater, so disable warning +#if defined(__clang__) +#pragma clang diagnostic ignored "-Wlanguage-extension-token" +#endif + +using namespace Scintilla; +using namespace Scintilla::Internal; + +namespace { + +#if defined(USE_D2D) + +D2D1_DRAW_TEXT_OPTIONS d2dDrawTextOptions = D2D1_DRAW_TEXT_OPTIONS_NONE; + +HMODULE hDLLD2D{}; +HMODULE hDLLD3D{}; +HMODULE hDLLDWrite{}; + +PFN_D3D11_CREATE_DEVICE fnDCD{}; + +void LoadD2DOnce() noexcept { + DWORD loadLibraryFlags = 0; + HMODULE kernel32 = ::GetModuleHandleW(L"kernel32.dll"); + if (kernel32) { + if (::GetProcAddress(kernel32, "SetDefaultDllDirectories")) { + // Availability of SetDefaultDllDirectories implies Windows 8+ or + // that KB2533623 has been installed so LoadLibraryEx can be called + // with LOAD_LIBRARY_SEARCH_SYSTEM32. + loadLibraryFlags = LOAD_LIBRARY_SEARCH_SYSTEM32; + } + } + + using D2D1CFSig = HRESULT(WINAPI *)(D2D1_FACTORY_TYPE factoryType, REFIID riid, + CONST D2D1_FACTORY_OPTIONS *pFactoryOptions, IUnknown **factory); + using DWriteCFSig = HRESULT(WINAPI *)(DWRITE_FACTORY_TYPE factoryType, REFIID iid, + IUnknown **factory); + + hDLLD2D = ::LoadLibraryEx(TEXT("D2D1.DLL"), {}, loadLibraryFlags); + D2D1CFSig fnD2DCF = DLLFunction<D2D1CFSig>(hDLLD2D, "D2D1CreateFactory"); + if (fnD2DCF) { + const D2D1_FACTORY_OPTIONS options{}; + // A multi threaded factory in case Scintilla is used with multiple GUI threads + fnD2DCF(D2D1_FACTORY_TYPE_MULTI_THREADED, + __uuidof(ID2D1Factory1), + &options, + reinterpret_cast<IUnknown **>(&pD2DFactory)); + } + hDLLDWrite = ::LoadLibraryEx(TEXT("DWRITE.DLL"), {}, loadLibraryFlags); + DWriteCFSig fnDWCF = DLLFunction<DWriteCFSig>(hDLLDWrite, "DWriteCreateFactory"); + if (fnDWCF) { + const GUID IID_IDWriteFactory2 = // 0439fc60-ca44-4994-8dee-3a9af7b732ec + { 0x0439fc60, 0xca44, 0x4994, { 0x8d, 0xee, 0x3a, 0x9a, 0xf7, 0xb7, 0x32, 0xec } }; + + const HRESULT hr = fnDWCF(DWRITE_FACTORY_TYPE_SHARED, + IID_IDWriteFactory2, + reinterpret_cast<IUnknown **>(&pIDWriteFactory)); + if (SUCCEEDED(hr)) { + // D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT + d2dDrawTextOptions = static_cast<D2D1_DRAW_TEXT_OPTIONS>(0x00000004); + } else { + fnDWCF(DWRITE_FACTORY_TYPE_SHARED, + __uuidof(IDWriteFactory1), + reinterpret_cast<IUnknown **>(&pIDWriteFactory)); + } + } + + hDLLD3D = ::LoadLibraryEx(TEXT("D3D11.DLL"), {}, loadLibraryFlags); + if (!hDLLD3D) { + Platform::DebugPrintf("Direct3D not loaded\n"); + } + fnDCD = DLLFunction<PFN_D3D11_CREATE_DEVICE>(hDLLD3D, "D3D11CreateDevice"); + if (!fnDCD) { + Platform::DebugPrintf("Direct3D does not have D3D11CreateDevice\n"); + } +} + +constexpr D2D1_TEXT_ANTIALIAS_MODE DWriteMapFontQuality(FontQuality extraFontFlag) noexcept { + switch (extraFontFlag & FontQuality::QualityMask) { + + case FontQuality::QualityNonAntialiased: + return D2D1_TEXT_ANTIALIAS_MODE_ALIASED; + + case FontQuality::QualityAntialiased: + return D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE; + + case FontQuality::QualityLcdOptimized: + return D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE; + + default: + return D2D1_TEXT_ANTIALIAS_MODE_DEFAULT; + } +} + +struct FontDirectWrite : public FontWin { + ComPtr<IDWriteTextFormat> pTextFormat; + FontQuality extraFontFlag = FontQuality::QualityDefault; + CharacterSet characterSet = CharacterSet::Ansi; + FLOAT yAscent = 2.0f; + FLOAT yDescent = 1.0f; + FLOAT yInternalLeading = 0.0f; + + explicit FontDirectWrite(const FontParameters &fp) : + extraFontFlag(fp.extraFontFlag), + characterSet(fp.characterSet) { + const std::wstring wsFace = WStringFromUTF8(fp.faceName); + const std::wstring wsLocale = WStringFromUTF8(fp.localeName); + const FLOAT fHeight = static_cast<FLOAT>(fp.size); + const DWRITE_FONT_STYLE style = fp.italic ? DWRITE_FONT_STYLE_ITALIC : DWRITE_FONT_STYLE_NORMAL; + HRESULT hr = pIDWriteFactory->CreateTextFormat(wsFace.c_str(), nullptr, + static_cast<DWRITE_FONT_WEIGHT>(fp.weight), + style, + static_cast<DWRITE_FONT_STRETCH>(fp.stretch), + fHeight, wsLocale.c_str(), pTextFormat.GetAddressOf()); + if (hr == E_INVALIDARG) { + // Possibly a bad locale name like "/" so try "en-us". + hr = pIDWriteFactory->CreateTextFormat(wsFace.c_str(), nullptr, + static_cast<DWRITE_FONT_WEIGHT>(fp.weight), + style, + static_cast<DWRITE_FONT_STRETCH>(fp.stretch), + fHeight, L"en-us", pTextFormat.ReleaseAndGetAddressOf()); + } + if (SUCCEEDED(hr)) { + pTextFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP); + + if (TextLayout pTextLayout = LayoutCreate(L"X", pTextFormat.Get())) { + constexpr int maxLines = 2; + DWRITE_LINE_METRICS lineMetrics[maxLines]{}; + UINT32 lineCount = 0; + hr = pTextLayout->GetLineMetrics(lineMetrics, maxLines, &lineCount); + if (SUCCEEDED(hr)) { + yAscent = lineMetrics[0].baseline; + yDescent = lineMetrics[0].height - lineMetrics[0].baseline; + + FLOAT emHeight; + hr = pTextLayout->GetFontSize(0, &emHeight); + if (SUCCEEDED(hr)) { + yInternalLeading = lineMetrics[0].height - emHeight; + } + } + pTextFormat->SetLineSpacing(DWRITE_LINE_SPACING_METHOD_UNIFORM, lineMetrics[0].height, lineMetrics[0].baseline); + } + } + } + // Allow copy constructor. Has to explicitly copy each field since can't use =default as deleted in Font. + FontDirectWrite(const FontDirectWrite &other) noexcept { + pTextFormat = other.pTextFormat; + extraFontFlag = other.extraFontFlag; + characterSet = other.characterSet; + yAscent = other.yAscent; + yDescent = other.yDescent; + yInternalLeading = other.yInternalLeading; + } + // Deleted so FontDirectWrite objects can not be copied. + FontDirectWrite(FontDirectWrite &&) = delete; + FontDirectWrite &operator=(const FontDirectWrite &) = delete; + FontDirectWrite &operator=(FontDirectWrite &&) = delete; + ~FontDirectWrite() noexcept override = default; + [[nodiscard]] HFONT HFont() const noexcept override { + LOGFONTW lf = {}; + const HRESULT hr = pTextFormat->GetFontFamilyName(lf.lfFaceName, LF_FACESIZE); + if (!SUCCEEDED(hr)) { + return {}; + } + lf.lfWeight = pTextFormat->GetFontWeight(); + lf.lfItalic = pTextFormat->GetFontStyle() == DWRITE_FONT_STYLE_ITALIC; + lf.lfHeight = -static_cast<int>(pTextFormat->GetFontSize()); + return ::CreateFontIndirectW(&lf); + } + + [[nodiscard]] int CodePageText(int codePage) const noexcept { + if (!(codePage == CpUtf8) && (characterSet != CharacterSet::Ansi)) { + codePage = CodePageFromCharSet(characterSet, codePage); + } + return codePage; + } + + static const FontDirectWrite *Cast(const Font *font_) { + const FontDirectWrite *pfm = dynamic_cast<const FontDirectWrite *>(font_); + PLATFORM_ASSERT(pfm); + if (!pfm) { + throw std::runtime_error("SurfaceD2D::SetFont: wrong Font type."); + } + return pfm; + } + + [[nodiscard]] std::unique_ptr<FontWin> Duplicate() const override { + return std::make_unique<FontDirectWrite>(*this); + } + + [[nodiscard]] CharacterSet GetCharacterSet() const noexcept override { + return characterSet; + } +}; + +constexpr D2D1_RECT_F RectangleFromPRectangle(PRectangle rc) noexcept { + return { + static_cast<FLOAT>(rc.left), + static_cast<FLOAT>(rc.top), + static_cast<FLOAT>(rc.right), + static_cast<FLOAT>(rc.bottom) + }; +} + +constexpr D2D1_POINT_2F DPointFromPoint(Point point) noexcept { + return { static_cast<FLOAT>(point.x), static_cast<FLOAT>(point.y) }; +} + +constexpr Supports SupportsD2D[] = { + Supports::LineDrawsFinal, + Supports::FractionalStrokeWidth, + Supports::TranslucentStroke, + Supports::PixelModification, + Supports::ThreadSafeMeasureWidths, +}; + +constexpr D2D1_RECT_F RectangleInset(D2D1_RECT_F rect, FLOAT inset) noexcept { + return D2D1_RECT_F{ + rect.left + inset, + rect.top + inset, + rect.right - inset, + rect.bottom - inset }; +} + +constexpr D2D_COLOR_F ColorFromColourAlpha(ColourRGBA colour) noexcept { + return D2D_COLOR_F{ + colour.GetRedComponent(), + colour.GetGreenComponent(), + colour.GetBlueComponent(), + colour.GetAlphaComponent() + }; +} + +class BlobInline; + +class SurfaceD2D : public Surface, public ISetRenderingParams { + SurfaceMode mode; + + // Text measuring surface: both pRenderTarget and pBitmapRenderTarget are null. + // Window surface: pRenderTarget is valid but not pBitmapRenderTarget. + // Bitmap drawing surface: both pRenderTarget and pBitmapRenderTarget are valid and the same. + ComPtr<ID2D1RenderTarget> pRenderTarget; + ComPtr<ID2D1BitmapRenderTarget> pBitmapRenderTarget; + int clipsActive = 0; + + BrushSolid pBrush = nullptr; + + static constexpr FontQuality invalidFontQuality = FontQuality::QualityMask; + FontQuality fontQuality = invalidFontQuality; + int logPixelsY = USER_DEFAULT_SCREEN_DPI; + int deviceScaleFactor = 1; + std::shared_ptr<RenderingParams> renderingParams; + + void Clear() noexcept; + void SetFontQuality(FontQuality extraFontFlag); + HRESULT GetBitmap(ID2D1Bitmap **ppBitmap); + void SetDeviceScaleFactor(const ID2D1RenderTarget *const pD2D1RenderTarget) noexcept; + +public: + SurfaceD2D() noexcept = default; + SurfaceD2D(ID2D1RenderTarget *pRenderTargetCompatible, int width, int height, SurfaceMode mode_, int logPixelsY_) noexcept; + // Deleted so SurfaceD2D objects can not be copied. + SurfaceD2D(const SurfaceD2D &) = delete; + SurfaceD2D(SurfaceD2D &&) = delete; + SurfaceD2D &operator=(const SurfaceD2D &) = delete; + SurfaceD2D &operator=(SurfaceD2D &&) = delete; + ~SurfaceD2D() noexcept override; + + void SetScale(WindowID wid) noexcept; + 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; + + void D2DPenColourAlpha(ColourRGBA fore) noexcept; + int LogPixelsY() override; + int PixelDivisions() override; + int DeviceHeightFont(int points) override; + void LineDraw(Point start, Point end, Stroke stroke) override; + static Geometry GeometricFigure(const Point *pts, size_t npts, D2D1_FIGURE_BEGIN figureBegin) noexcept; + 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, int codePageOverride, 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 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; + + void SetRenderingParams(std::shared_ptr<RenderingParams> renderingParams_) override; +}; + +SurfaceD2D::SurfaceD2D(ID2D1RenderTarget *pRenderTargetCompatible, int width, int height, SurfaceMode mode_, int logPixelsY_) noexcept { + const D2D1_SIZE_F desiredSize = D2D1::SizeF(static_cast<float>(width), static_cast<float>(height)); + D2D1_PIXEL_FORMAT desiredFormat; +#ifdef __MINGW32__ + desiredFormat.format = DXGI_FORMAT_UNKNOWN; +#else + desiredFormat = pRenderTargetCompatible->GetPixelFormat(); +#endif + desiredFormat.alphaMode = D2D1_ALPHA_MODE_IGNORE; + const HRESULT hr = pRenderTargetCompatible->CreateCompatibleRenderTarget( + &desiredSize, nullptr, &desiredFormat, D2D1_COMPATIBLE_RENDER_TARGET_OPTIONS_NONE, pBitmapRenderTarget.GetAddressOf()); + if (SUCCEEDED(hr)) { + pRenderTarget = pBitmapRenderTarget; + SetDeviceScaleFactor(pRenderTarget.Get()); + pRenderTarget->BeginDraw(); + } + mode = mode_; + logPixelsY = logPixelsY_; +} + +SurfaceD2D::~SurfaceD2D() noexcept { + Clear(); +} + +void SurfaceD2D::Clear() noexcept { + pBrush = nullptr; + if (pRenderTarget) { + while (clipsActive) { + pRenderTarget->PopAxisAlignedClip(); + clipsActive--; + } + if (pBitmapRenderTarget) { + pRenderTarget->EndDraw(); + } + } + pRenderTarget = nullptr; + pBitmapRenderTarget = nullptr; +} + +void SurfaceD2D::Release() noexcept { + Clear(); +} + +void SurfaceD2D::SetScale(WindowID wid) noexcept { + fontQuality = invalidFontQuality; + logPixelsY = DpiForWindow(wid); +} + +int SurfaceD2D::SupportsFeature(Supports feature) noexcept { + for (const Supports f : SupportsD2D) { + if (f == feature) + return 1; + } + return 0; +} + +bool SurfaceD2D::Initialised() { + return pRenderTarget; +} + +void SurfaceD2D::Init(WindowID wid) { + Release(); + SetScale(wid); +} + +void SurfaceD2D::Init(SurfaceID sid, WindowID wid) { + Release(); + SetScale(wid); + pRenderTarget = static_cast<ID2D1RenderTarget *>(sid); + SetDeviceScaleFactor(pRenderTarget.Get()); +} + +std::unique_ptr<Surface> SurfaceD2D::AllocatePixMap(int width, int height) { + std::unique_ptr<SurfaceD2D> surf = std::make_unique<SurfaceD2D>(pRenderTarget.Get(), width, height, mode, logPixelsY); + surf->SetRenderingParams(renderingParams); + return surf; +} + +void SurfaceD2D::SetMode(SurfaceMode mode_) { + mode = mode_; +} + +HRESULT SurfaceD2D::GetBitmap(ID2D1Bitmap **ppBitmap) { + PLATFORM_ASSERT(pBitmapRenderTarget); + return pBitmapRenderTarget->GetBitmap(ppBitmap); +} + +void SurfaceD2D::D2DPenColourAlpha(ColourRGBA fore) noexcept { + if (pRenderTarget) { + const D2D_COLOR_F col = ColorFromColourAlpha(fore); + if (pBrush) { + pBrush->SetColor(col); + } else { + const HRESULT hr = pRenderTarget->CreateSolidColorBrush(col, &pBrush); + if (!SUCCEEDED(hr)) { + pBrush = nullptr; + } + } + } +} + +void SurfaceD2D::SetFontQuality(FontQuality extraFontFlag) { + if ((fontQuality != extraFontFlag) && renderingParams) { + fontQuality = extraFontFlag; + const D2D1_TEXT_ANTIALIAS_MODE aaMode = DWriteMapFontQuality(extraFontFlag); + if (aaMode == D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE && renderingParams->customRenderingParams) { + pRenderTarget->SetTextRenderingParams(renderingParams->customRenderingParams.Get()); + } else if (renderingParams->defaultRenderingParams) { + pRenderTarget->SetTextRenderingParams(renderingParams->defaultRenderingParams.Get()); + } + pRenderTarget->SetTextAntialiasMode(aaMode); + } +} + +int SurfaceD2D::LogPixelsY() { + return logPixelsY; +} + +void SurfaceD2D::SetDeviceScaleFactor(const ID2D1RenderTarget *const pD2D1RenderTarget) noexcept { + FLOAT dpiX = 0.f; + FLOAT dpiY = 0.f; + pD2D1RenderTarget->GetDpi(&dpiX, &dpiY); + deviceScaleFactor = static_cast<int>(dpiX / dpiDefault); +} + +int SurfaceD2D::PixelDivisions() { + return deviceScaleFactor; +} + +int SurfaceD2D::DeviceHeightFont(int points) { + return ::MulDiv(points, LogPixelsY(), 72); +} + +void SurfaceD2D::LineDraw(Point start, Point end, Stroke stroke) { + D2DPenColourAlpha(stroke.colour); + + D2D1_STROKE_STYLE_PROPERTIES strokeProps {}; + strokeProps.startCap = D2D1_CAP_STYLE_SQUARE; + strokeProps.endCap = D2D1_CAP_STYLE_SQUARE; + strokeProps.dashCap = D2D1_CAP_STYLE_FLAT; + strokeProps.lineJoin = D2D1_LINE_JOIN_MITER; + strokeProps.miterLimit = 4.0f; + strokeProps.dashStyle = D2D1_DASH_STYLE_SOLID; + strokeProps.dashOffset = 0; + + // get the stroke style to apply + if (const StrokeStyle pStrokeStyle = StrokeStyleCreate(strokeProps)) { + pRenderTarget->DrawLine( + DPointFromPoint(start), + DPointFromPoint(end), pBrush.Get(), stroke.WidthF(), pStrokeStyle.Get()); + } +} + +Geometry SurfaceD2D::GeometricFigure(const Point *pts, size_t npts, D2D1_FIGURE_BEGIN figureBegin) noexcept { + Geometry geometry = GeometryCreate(); + if (geometry) { + if (const GeometrySink sink = GeometrySinkCreate(geometry.Get())) { + sink->BeginFigure(DPointFromPoint(pts[0]), figureBegin); + for (size_t i = 1; i < npts; i++) { + sink->AddLine(DPointFromPoint(pts[i])); + } + sink->EndFigure((figureBegin == D2D1_FIGURE_BEGIN_FILLED) ? + D2D1_FIGURE_END_CLOSED : D2D1_FIGURE_END_OPEN); + sink->Close(); + } + } + return geometry; +} + +void SurfaceD2D::PolyLine(const Point *pts, size_t npts, Stroke stroke) { + PLATFORM_ASSERT(pRenderTarget && (npts > 1)); + if (!pRenderTarget || (npts <= 1)) { + return; + } + + const Geometry geometry = GeometricFigure(pts, npts, D2D1_FIGURE_BEGIN_HOLLOW); + PLATFORM_ASSERT(geometry); + if (!geometry) { + return; + } + + D2DPenColourAlpha(stroke.colour); + D2D1_STROKE_STYLE_PROPERTIES strokeProps {}; + strokeProps.startCap = D2D1_CAP_STYLE_ROUND; + strokeProps.endCap = D2D1_CAP_STYLE_ROUND; + strokeProps.dashCap = D2D1_CAP_STYLE_FLAT; + strokeProps.lineJoin = D2D1_LINE_JOIN_MITER; + strokeProps.miterLimit = 4.0f; + strokeProps.dashStyle = D2D1_DASH_STYLE_SOLID; + strokeProps.dashOffset = 0; + + // get the stroke style to apply + if (const StrokeStyle pStrokeStyle = StrokeStyleCreate(strokeProps)) { + pRenderTarget->DrawGeometry(geometry.Get(), pBrush.Get(), stroke.WidthF(), pStrokeStyle.Get()); + } +} + +void SurfaceD2D::Polygon(const Point *pts, size_t npts, FillStroke fillStroke) { + PLATFORM_ASSERT(pRenderTarget && (npts > 2)); + if (pRenderTarget) { + const Geometry geometry = GeometricFigure(pts, npts, D2D1_FIGURE_BEGIN_FILLED); + PLATFORM_ASSERT(geometry); + if (geometry) { + D2DPenColourAlpha(fillStroke.fill.colour); + pRenderTarget->FillGeometry(geometry.Get(), pBrush.Get()); + D2DPenColourAlpha(fillStroke.stroke.colour); + pRenderTarget->DrawGeometry(geometry.Get(), pBrush.Get(), fillStroke.stroke.WidthF()); + } + } +} + +void SurfaceD2D::RectangleDraw(PRectangle rc, FillStroke fillStroke) { + if (!pRenderTarget) + return; + const D2D1_RECT_F rect = RectangleFromPRectangle(rc); + const D2D1_RECT_F rectFill = RectangleInset(rect, fillStroke.stroke.WidthF()); + const float halfStroke = fillStroke.stroke.WidthF() / 2.0f; + const D2D1_RECT_F rectOutline = RectangleInset(rect, halfStroke); + + D2DPenColourAlpha(fillStroke.fill.colour); + pRenderTarget->FillRectangle(&rectFill, pBrush.Get()); + D2DPenColourAlpha(fillStroke.stroke.colour); + pRenderTarget->DrawRectangle(&rectOutline, pBrush.Get(), fillStroke.stroke.WidthF()); +} + +void SurfaceD2D::RectangleFrame(PRectangle rc, Stroke stroke) { + if (pRenderTarget) { + const XYPOSITION halfStroke = stroke.width / 2.0f; + const D2D1_RECT_F rectangle1 = RectangleFromPRectangle(rc.Inset(halfStroke)); + D2DPenColourAlpha(stroke.colour); + pRenderTarget->DrawRectangle(&rectangle1, pBrush.Get(), stroke.WidthF()); + } +} + +void SurfaceD2D::FillRectangle(PRectangle rc, Fill fill) { + if (pRenderTarget) { + D2DPenColourAlpha(fill.colour); + const D2D1_RECT_F rectangle = RectangleFromPRectangle(rc); + pRenderTarget->FillRectangle(&rectangle, pBrush.Get()); + } +} + +void SurfaceD2D::FillRectangleAligned(PRectangle rc, Fill fill) { + FillRectangle(PixelAlign(rc, PixelDivisions()), fill); +} + +void SurfaceD2D::FillRectangle(PRectangle rc, Surface &surfacePattern) { + SurfaceD2D *psurfOther = dynamic_cast<SurfaceD2D *>(&surfacePattern); + PLATFORM_ASSERT(psurfOther); + if (!psurfOther) { + throw std::runtime_error("SurfaceD2D::FillRectangle: wrong Surface type."); + } + ComPtr<ID2D1Bitmap> pBitmap; + HRESULT hr = psurfOther->GetBitmap(pBitmap.GetAddressOf()); + if (SUCCEEDED(hr) && pBitmap) { + ComPtr<ID2D1BitmapBrush> pBitmapBrush; + const D2D1_BITMAP_BRUSH_PROPERTIES brushProperties = + D2D1::BitmapBrushProperties(D2D1_EXTEND_MODE_WRAP, D2D1_EXTEND_MODE_WRAP, + D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR); + // Create the bitmap brush. + hr = pRenderTarget->CreateBitmapBrush(pBitmap.Get(), brushProperties, pBitmapBrush.GetAddressOf()); + if (SUCCEEDED(hr) && pBitmapBrush) { + pRenderTarget->FillRectangle( + RectangleFromPRectangle(rc), + pBitmapBrush.Get()); + } + } +} + +void SurfaceD2D::RoundedRectangle(PRectangle rc, FillStroke fillStroke) { + if (pRenderTarget) { + const FLOAT minDimension = static_cast<FLOAT>(std::min(rc.Width(), rc.Height())) / 2.0f; + const FLOAT radius = std::min(4.0f, minDimension); + if (fillStroke.fill.colour == fillStroke.stroke.colour) { + const D2D1_ROUNDED_RECT roundedRectFill = { + RectangleFromPRectangle(rc), + radius, radius }; + D2DPenColourAlpha(fillStroke.fill.colour); + pRenderTarget->FillRoundedRectangle(roundedRectFill, pBrush.Get()); + } else { + const D2D1_ROUNDED_RECT roundedRectFill = { + RectangleFromPRectangle(rc.Inset(1.0)), + radius-1, radius-1 }; + D2DPenColourAlpha(fillStroke.fill.colour); + pRenderTarget->FillRoundedRectangle(roundedRectFill, pBrush.Get()); + + const D2D1_ROUNDED_RECT roundedRect = { + RectangleFromPRectangle(rc.Inset(0.5)), + radius, radius }; + D2DPenColourAlpha(fillStroke.stroke.colour); + pRenderTarget->DrawRoundedRectangle(roundedRect, pBrush.Get(), fillStroke.stroke.WidthF()); + } + } +} + +void SurfaceD2D::AlphaRectangle(PRectangle rc, XYPOSITION cornerSize, FillStroke fillStroke) { + const D2D1_RECT_F rect = RectangleFromPRectangle(rc); + const D2D1_RECT_F rectFill = RectangleInset(rect, fillStroke.stroke.WidthF()); + const float halfStroke = fillStroke.stroke.WidthF() / 2.0f; + const D2D1_RECT_F rectOutline = RectangleInset(rect, halfStroke); + if (pRenderTarget) { + if (cornerSize == 0) { + // When corner size is zero, draw square rectangle to prevent blurry pixels at corners + D2DPenColourAlpha(fillStroke.fill.colour); + pRenderTarget->FillRectangle(rectFill, pBrush.Get()); + + D2DPenColourAlpha(fillStroke.stroke.colour); + pRenderTarget->DrawRectangle(rectOutline, pBrush.Get(), fillStroke.stroke.WidthF()); + } else { + const float cornerSizeF = static_cast<float>(cornerSize); + const D2D1_ROUNDED_RECT roundedRectFill = { + rectFill, cornerSizeF - 1.0f, cornerSizeF - 1.0f }; + D2DPenColourAlpha(fillStroke.fill.colour); + pRenderTarget->FillRoundedRectangle(roundedRectFill, pBrush.Get()); + + const D2D1_ROUNDED_RECT roundedRect = { + rectOutline, cornerSizeF, cornerSizeF}; + D2DPenColourAlpha(fillStroke.stroke.colour); + pRenderTarget->DrawRoundedRectangle(roundedRect, pBrush.Get(), fillStroke.stroke.WidthF()); + } + } +} + +void SurfaceD2D::GradientRectangle(PRectangle rc, const std::vector<ColourStop> &stops, GradientOptions options) { + if (pRenderTarget) { + D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES lgbp { + DPointFromPoint(Point(rc.left, rc.top)), {} + }; + switch (options) { + case GradientOptions::leftToRight: + lgbp.endPoint = DPointFromPoint(Point(rc.right, rc.top)); + break; + case GradientOptions::topToBottom: + default: + lgbp.endPoint = DPointFromPoint(Point(rc.left, rc.bottom)); + break; + } + + std::vector<D2D1_GRADIENT_STOP> gradientStops; + for (const ColourStop &stop : stops) { + gradientStops.push_back({ static_cast<FLOAT>(stop.position), ColorFromColourAlpha(stop.colour) }); + } + ComPtr<ID2D1GradientStopCollection> pGradientStops; + HRESULT hr = pRenderTarget->CreateGradientStopCollection( + gradientStops.data(), static_cast<UINT32>(gradientStops.size()), pGradientStops.GetAddressOf()); + if (FAILED(hr) || !pGradientStops) { + return; + } + ComPtr<ID2D1LinearGradientBrush> pBrushLinear; + hr = pRenderTarget->CreateLinearGradientBrush( + lgbp, pGradientStops.Get(), pBrushLinear.GetAddressOf()); + if (SUCCEEDED(hr) && pBrushLinear) { + const D2D1_RECT_F rectangle = RectangleFromPRectangle(PRectangle( + std::round(rc.left), rc.top, std::round(rc.right), rc.bottom)); + pRenderTarget->FillRectangle(&rectangle, pBrushLinear.Get()); + } + } +} + +void SurfaceD2D::DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) { + if (pRenderTarget) { + 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; + + std::vector<unsigned char> image(RGBAImage::bytesPerPixel * height * width); + RGBAImage::BGRAFromRGBA(image.data(), pixelsImage, static_cast<ptrdiff_t>(height) * width); + + ComPtr<ID2D1Bitmap> bitmap; + const D2D1_SIZE_U size = D2D1::SizeU(width, height); + const D2D1_BITMAP_PROPERTIES props = {{DXGI_FORMAT_B8G8R8A8_UNORM, + D2D1_ALPHA_MODE_PREMULTIPLIED}, 72.0, 72.0}; + const HRESULT hr = pRenderTarget->CreateBitmap(size, image.data(), + width * 4, &props, bitmap.GetAddressOf()); + if (SUCCEEDED(hr)) { + const D2D1_RECT_F rcDestination = RectangleFromPRectangle(rc); + pRenderTarget->DrawBitmap(bitmap.Get(), rcDestination); + } + } +} + +void SurfaceD2D::Ellipse(PRectangle rc, FillStroke fillStroke) { + if (!pRenderTarget) + return; + const D2D1_POINT_2F centre = DPointFromPoint(rc.Centre()); + + const FLOAT radiusFill = static_cast<FLOAT>(rc.Width() / 2.0f - fillStroke.stroke.width); + const D2D1_ELLIPSE ellipseFill = { centre, radiusFill, radiusFill }; + + D2DPenColourAlpha(fillStroke.fill.colour); + pRenderTarget->FillEllipse(ellipseFill, pBrush.Get()); + + const FLOAT radiusOutline = static_cast<FLOAT>(rc.Width() / 2.0f - fillStroke.stroke.width / 2.0f); + const D2D1_ELLIPSE ellipseOutline = { centre, radiusOutline, radiusOutline }; + + D2DPenColourAlpha(fillStroke.stroke.colour); + pRenderTarget->DrawEllipse(ellipseOutline, pBrush.Get(), fillStroke.stroke.WidthF()); +} + +void SurfaceD2D::Stadium(PRectangle rc, FillStroke fillStroke, Ends ends) { + if (!pRenderTarget) + return; + if (rc.Width() < rc.Height()) { + // Can't draw nice ends so just draw a rectangle + RectangleDraw(rc, fillStroke); + return; + } + const FLOAT radius = static_cast<FLOAT>(rc.Height() / 2.0); + const FLOAT radiusFill = radius - fillStroke.stroke.WidthF(); + const FLOAT halfStroke = fillStroke.stroke.WidthF() / 2.0f; + if (ends == Surface::Ends::semiCircles) { + const D2D1_RECT_F rect = RectangleFromPRectangle(rc); + const D2D1_ROUNDED_RECT roundedRectFill = { RectangleInset(rect, fillStroke.stroke.WidthF()), + radiusFill, radiusFill }; + D2DPenColourAlpha(fillStroke.fill.colour); + pRenderTarget->FillRoundedRectangle(roundedRectFill, pBrush.Get()); + + const D2D1_ROUNDED_RECT roundedRect = { RectangleInset(rect, halfStroke), + radius, radius }; + D2DPenColourAlpha(fillStroke.stroke.colour); + pRenderTarget->DrawRoundedRectangle(roundedRect, pBrush.Get(), fillStroke.stroke.WidthF()); + } else { + const Ends leftSide = static_cast<Ends>(static_cast<int>(ends) & 0xf); + const Ends rightSide = static_cast<Ends>(static_cast<int>(ends) & 0xf0); + PRectangle rcInner = rc; + rcInner.left += radius; + rcInner.right -= radius; + const Geometry pathGeometry = GeometryCreate(); + if (!pathGeometry) + return; + if (const GeometrySink pSink = GeometrySinkCreate(pathGeometry.Get())) { + switch (leftSide) { + case Ends::leftFlat: + pSink->BeginFigure(DPointFromPoint(Point(rc.left + halfStroke, rc.top + halfStroke)), D2D1_FIGURE_BEGIN_FILLED); + pSink->AddLine(DPointFromPoint(Point(rc.left + halfStroke, rc.bottom - halfStroke))); + break; + case Ends::leftAngle: + pSink->BeginFigure(DPointFromPoint(Point(rcInner.left + halfStroke, rc.top + halfStroke)), D2D1_FIGURE_BEGIN_FILLED); + pSink->AddLine(DPointFromPoint(Point(rc.left + halfStroke, rc.Centre().y))); + pSink->AddLine(DPointFromPoint(Point(rcInner.left + halfStroke, rc.bottom - halfStroke))); + break; + case Ends::semiCircles: + default: { + pSink->BeginFigure(DPointFromPoint(Point(rcInner.left + halfStroke, rc.top + halfStroke)), D2D1_FIGURE_BEGIN_FILLED); + D2D1_ARC_SEGMENT segment{}; + segment.point = DPointFromPoint(Point(rcInner.left + halfStroke, rc.bottom - halfStroke)); + segment.size = D2D1::SizeF(radiusFill, radiusFill); + segment.rotationAngle = 0.0f; + segment.sweepDirection = D2D1_SWEEP_DIRECTION_COUNTER_CLOCKWISE; + segment.arcSize = D2D1_ARC_SIZE_SMALL; + pSink->AddArc(segment); + } + break; + } + + switch (rightSide) { + case Ends::rightFlat: + pSink->AddLine(DPointFromPoint(Point(rc.right - halfStroke, rc.bottom - halfStroke))); + pSink->AddLine(DPointFromPoint(Point(rc.right - halfStroke, rc.top + halfStroke))); + break; + case Ends::rightAngle: + pSink->AddLine(DPointFromPoint(Point(rcInner.right - halfStroke, rc.bottom - halfStroke))); + pSink->AddLine(DPointFromPoint(Point(rc.right - halfStroke, rc.Centre().y))); + pSink->AddLine(DPointFromPoint(Point(rcInner.right - halfStroke, rc.top + halfStroke))); + break; + case Ends::semiCircles: + default: { + pSink->AddLine(DPointFromPoint(Point(rcInner.right - halfStroke, rc.bottom - halfStroke))); + D2D1_ARC_SEGMENT segment{}; + segment.point = DPointFromPoint(Point(rcInner.right - halfStroke, rc.top + halfStroke)); + segment.size = D2D1::SizeF(radiusFill, radiusFill); + segment.rotationAngle = 0.0f; + segment.sweepDirection = D2D1_SWEEP_DIRECTION_COUNTER_CLOCKWISE; + segment.arcSize = D2D1_ARC_SIZE_SMALL; + pSink->AddArc(segment); + } + break; + } + + pSink->EndFigure(D2D1_FIGURE_END_CLOSED); + + pSink->Close(); + } + D2DPenColourAlpha(fillStroke.fill.colour); + pRenderTarget->FillGeometry(pathGeometry.Get(), pBrush.Get()); + D2DPenColourAlpha(fillStroke.stroke.colour); + pRenderTarget->DrawGeometry(pathGeometry.Get(), pBrush.Get(), fillStroke.stroke.WidthF()); + } +} + +void SurfaceD2D::Copy(PRectangle rc, Point from, Surface &surfaceSource) { + SurfaceD2D &surfOther = dynamic_cast<SurfaceD2D &>(surfaceSource); + ComPtr<ID2D1Bitmap> pBitmap; + const HRESULT hr = surfOther.GetBitmap(pBitmap.GetAddressOf()); + if (SUCCEEDED(hr) && pBitmap) { + const D2D1_RECT_F rcDestination = RectangleFromPRectangle(rc); + const D2D1_RECT_F rcSource = RectangleFromPRectangle(PRectangle( + from.x, from.y, from.x + rc.Width(), from.y + rc.Height())); + pRenderTarget->DrawBitmap(pBitmap.Get(), rcDestination, 1.0f, + D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR, rcSource); + } +} + +class BlobInline final : public IDWriteInlineObject { + XYPOSITION width; + + // IUnknown + STDMETHODIMP QueryInterface(REFIID riid, PVOID *ppv) override; + STDMETHODIMP_(ULONG)AddRef() override; + STDMETHODIMP_(ULONG)Release() override; + + // IDWriteInlineObject + COM_DECLSPEC_NOTHROW STDMETHODIMP Draw( + void *clientDrawingContext, + IDWriteTextRenderer *renderer, + FLOAT originX, + FLOAT originY, + BOOL isSideways, + BOOL isRightToLeft, + IUnknown *clientDrawingEffect + ) override; + COM_DECLSPEC_NOTHROW STDMETHODIMP GetMetrics(DWRITE_INLINE_OBJECT_METRICS *metrics) override; + COM_DECLSPEC_NOTHROW STDMETHODIMP GetOverhangMetrics(DWRITE_OVERHANG_METRICS *overhangs) override; + COM_DECLSPEC_NOTHROW STDMETHODIMP GetBreakConditions( + DWRITE_BREAK_CONDITION *breakConditionBefore, + DWRITE_BREAK_CONDITION *breakConditionAfter) override; +public: + explicit BlobInline(XYPOSITION width_=0.0f) noexcept : width(width_) { + } +}; + +/// Implement IUnknown +STDMETHODIMP BlobInline::QueryInterface(REFIID riid, PVOID *ppv) { + if (!ppv) + return E_POINTER; + // Never called so not checked. + *ppv = nullptr; + if (riid == IID_IUnknown) + *ppv = this; + if (riid == __uuidof(IDWriteInlineObject)) + *ppv = this; + if (!*ppv) + return E_NOINTERFACE; + return S_OK; +} + +STDMETHODIMP_(ULONG) BlobInline::AddRef() { + // Lifetime tied to Platform methods so ignore any reference operations. + return 1; +} + +STDMETHODIMP_(ULONG) BlobInline::Release() { + // Lifetime tied to Platform methods so ignore any reference operations. + return 1; +} + +/// Implement IDWriteInlineObject +COM_DECLSPEC_NOTHROW HRESULT STDMETHODCALLTYPE BlobInline::Draw( + void*, + IDWriteTextRenderer*, + FLOAT, + FLOAT, + BOOL, + BOOL, + IUnknown*) { + // Since not performing drawing, not necessary to implement + // Could be implemented by calling back into platform-independent code. + // This would allow more of the drawing to be mediated through DirectWrite. + return S_OK; +} + +COM_DECLSPEC_NOTHROW HRESULT STDMETHODCALLTYPE BlobInline::GetMetrics( + DWRITE_INLINE_OBJECT_METRICS *metrics +) { + if (!metrics) + return E_POINTER; + metrics->width = static_cast<FLOAT>(width); + metrics->height = 2; + metrics->baseline = 1; + metrics->supportsSideways = FALSE; + return S_OK; +} + +COM_DECLSPEC_NOTHROW HRESULT STDMETHODCALLTYPE BlobInline::GetOverhangMetrics( + DWRITE_OVERHANG_METRICS *overhangs +) { + if (!overhangs) + return E_POINTER; + overhangs->left = 0; + overhangs->top = 0; + overhangs->right = 0; + overhangs->bottom = 0; + return S_OK; +} + +COM_DECLSPEC_NOTHROW HRESULT STDMETHODCALLTYPE BlobInline::GetBreakConditions( + DWRITE_BREAK_CONDITION *breakConditionBefore, + DWRITE_BREAK_CONDITION *breakConditionAfter +) { + if (!breakConditionBefore || !breakConditionAfter) + return E_POINTER; + // Since not performing 2D layout, not necessary to implement + *breakConditionBefore = DWRITE_BREAK_CONDITION_NEUTRAL; + *breakConditionAfter = DWRITE_BREAK_CONDITION_NEUTRAL; + return S_OK; +} + +class ScreenLineLayout : public IScreenLineLayout { + TextLayout textLayout; + std::string text; + std::wstring buffer; + std::vector<BlobInline> blobs; + static void FillTextLayoutFormats(const IScreenLine *screenLine, IDWriteTextLayout *textLayout, std::vector<BlobInline> &blobs); + static std::wstring ReplaceRepresentation(std::string_view text); + static size_t GetPositionInLayout(std::string_view text, size_t position); +public: + explicit ScreenLineLayout(const IScreenLine *screenLine); + // Deleted so ScreenLineLayout objects can not be copied + ScreenLineLayout(const ScreenLineLayout &) = delete; + ScreenLineLayout(ScreenLineLayout &&) = delete; + ScreenLineLayout &operator=(const ScreenLineLayout &) = delete; + ScreenLineLayout &operator=(ScreenLineLayout &&) = delete; + ~ScreenLineLayout() noexcept override = default; + size_t PositionFromX(XYPOSITION xDistance, bool charPosition) override; + XYPOSITION XFromPosition(size_t caretPosition) override; + std::vector<Interval> FindRangeIntervals(size_t start, size_t end) override; +}; + +// Each char can have its own style, so we fill the textLayout with the textFormat of each char + +void ScreenLineLayout::FillTextLayoutFormats(const IScreenLine *screenLine, IDWriteTextLayout *textLayout, std::vector<BlobInline> &blobs) { + // Reserve enough entries up front so they are not moved and the pointers handed + // to textLayout remain valid. + const ptrdiff_t numRepresentations = screenLine->RepresentationCount(); + std::string_view text = screenLine->Text(); + const ptrdiff_t numTabs = std::count(std::begin(text), std::end(text), '\t'); + blobs.reserve(numRepresentations + numTabs); + + UINT32 layoutPosition = 0; + + for (size_t bytePosition = 0; bytePosition < screenLine->Length();) { + const unsigned char uch = screenLine->Text()[bytePosition]; + const unsigned int byteCount = UTF8BytesOfLead[uch]; + const UINT32 codeUnits = UTF16LengthFromUTF8ByteCount(byteCount); + const DWRITE_TEXT_RANGE textRange = { layoutPosition, codeUnits }; + + XYPOSITION representationWidth = screenLine->RepresentationWidth(bytePosition); + if ((representationWidth == 0.0f) && (screenLine->Text()[bytePosition] == '\t')) { + D2D1_POINT_2F realPt {}; + DWRITE_HIT_TEST_METRICS realCaretMetrics {}; + textLayout->HitTestTextPosition( + layoutPosition, + false, // trailing if false, else leading edge + &realPt.x, + &realPt.y, + &realCaretMetrics + ); + + const XYPOSITION nextTab = screenLine->TabPositionAfter(realPt.x); + representationWidth = nextTab - realPt.x; + } + if (representationWidth > 0.0f) { + blobs.push_back(BlobInline(representationWidth)); + textLayout->SetInlineObject(&blobs.back(), textRange); + }; + + const FontDirectWrite *pfm = + dynamic_cast<const FontDirectWrite *>(screenLine->FontOfPosition(bytePosition)); + if (!pfm) { + throw std::runtime_error("FillTextLayoutFormats: wrong Font type."); + } + + const unsigned int fontFamilyNameSize = pfm->pTextFormat->GetFontFamilyNameLength(); + std::wstring fontFamilyName(fontFamilyNameSize, 0); + const HRESULT hrFamily = pfm->pTextFormat->GetFontFamilyName(fontFamilyName.data(), fontFamilyNameSize + 1); + if (SUCCEEDED(hrFamily)) { + textLayout->SetFontFamilyName(fontFamilyName.c_str(), textRange); + } + + textLayout->SetFontSize(pfm->pTextFormat->GetFontSize(), textRange); + textLayout->SetFontWeight(pfm->pTextFormat->GetFontWeight(), textRange); + textLayout->SetFontStyle(pfm->pTextFormat->GetFontStyle(), textRange); + + const unsigned int localeNameSize = pfm->pTextFormat->GetLocaleNameLength(); + std::wstring localeName(localeNameSize, 0); + const HRESULT hrLocale = pfm->pTextFormat->GetLocaleName(localeName.data(), localeNameSize + 1); + if (SUCCEEDED(hrLocale)) { + textLayout->SetLocaleName(localeName.c_str(), textRange); + } + + textLayout->SetFontStretch(pfm->pTextFormat->GetFontStretch(), textRange); + + IDWriteFontCollection *fontCollection = nullptr; + if (SUCCEEDED(pfm->pTextFormat->GetFontCollection(&fontCollection))) { + textLayout->SetFontCollection(fontCollection, textRange); + } + + bytePosition += byteCount; + layoutPosition += codeUnits; + } + +} + +/* Convert to a wide character string and replace tabs with X to stop DirectWrite tab expansion */ + +std::wstring ScreenLineLayout::ReplaceRepresentation(std::string_view text) { + const TextWide wideText(text, CpUtf8); + std::wstring ws(wideText.buffer, wideText.tlen); + std::replace(ws.begin(), ws.end(), L'\t', L'X'); + return ws; +} + +// Finds the position in the wide character version of the text. + +size_t ScreenLineLayout::GetPositionInLayout(std::string_view text, size_t position) { + const std::string_view textUptoPosition = text.substr(0, position); + return UTF16Length(textUptoPosition); +} + +ScreenLineLayout::ScreenLineLayout(const IScreenLine *screenLine) { + // If the text is empty, then no need to go through this function + if (!screenLine || !screenLine->Length()) + return; + + text = screenLine->Text(); + + // Get textFormat + const FontDirectWrite *pfm = FontDirectWrite::Cast(screenLine->FontOfPosition(0)); + if (!pfm->pTextFormat) { + return; + } + + // Convert the string to wstring and replace the original control characters with their representative chars. + buffer = ReplaceRepresentation(screenLine->Text()); + + // Create a text layout + textLayout = LayoutCreate( + buffer, + pfm->pTextFormat.Get(), + static_cast<FLOAT>(screenLine->Width()), + static_cast<FLOAT>(screenLine->Height())); + if (!textLayout) { + return; + } + + // Fill the textLayout chars with their own formats + FillTextLayoutFormats(screenLine, textLayout.Get(), blobs); +} + +// Get the position from the provided x + +size_t ScreenLineLayout::PositionFromX(XYPOSITION xDistance, bool charPosition) { + if (!textLayout) { + return 0; + } + + // Returns the text position corresponding to the mouse x,y. + // If hitting the trailing side of a cluster, return the + // leading edge of the following text position. + + BOOL isTrailingHit = FALSE; + BOOL isInside = FALSE; + DWRITE_HIT_TEST_METRICS caretMetrics {}; + + textLayout->HitTestPoint( + static_cast<FLOAT>(xDistance), + 0.0f, + &isTrailingHit, + &isInside, + &caretMetrics + ); + + DWRITE_HIT_TEST_METRICS hitTestMetrics {}; + if (isTrailingHit) { + FLOAT caretX = 0.0f; + FLOAT caretY = 0.0f; + + // Uses hit-testing to align the current caret position to a whole cluster, + // rather than residing in the middle of a base character + diacritic, + // surrogate pair, or character + UVS. + + // Align the caret to the nearest whole cluster. + textLayout->HitTestTextPosition( + caretMetrics.textPosition, + false, + &caretX, + &caretY, + &hitTestMetrics + ); + } + + size_t pos; + if (charPosition) { + pos = isTrailingHit ? hitTestMetrics.textPosition : caretMetrics.textPosition; + } else { + pos = isTrailingHit ? static_cast<size_t>(hitTestMetrics.textPosition) + hitTestMetrics.length : caretMetrics.textPosition; + } + + // Get the character position in original string + return UTF8PositionFromUTF16Position(text, pos); +} + +// Finds the point of the caret position + +XYPOSITION ScreenLineLayout::XFromPosition(size_t caretPosition) { + if (!textLayout) { + return 0.0; + } + // Convert byte positions to wchar_t positions + const size_t position = GetPositionInLayout(text, caretPosition); + + // Translate text character offset to point x,y. + DWRITE_HIT_TEST_METRICS caretMetrics {}; + D2D1_POINT_2F pt {}; + + textLayout->HitTestTextPosition( + static_cast<UINT32>(position), + false, // trailing if false, else leading edge + &pt.x, + &pt.y, + &caretMetrics + ); + + return pt.x; +} + +// Find the selection range rectangles + +std::vector<Interval> ScreenLineLayout::FindRangeIntervals(size_t start, size_t end) { + std::vector<Interval> ret; + + if (!textLayout || (start == end)) { + return ret; + } + + // Convert byte positions to wchar_t positions + const size_t startPos = GetPositionInLayout(text, start); + const size_t endPos = GetPositionInLayout(text, end); + + // Find selection range length + const size_t rangeLength = (endPos > startPos) ? (endPos - startPos) : (startPos - endPos); + + // Determine actual number of hit-test ranges + UINT32 actualHitTestCount = 0; + + // First try with 2 elements and if more needed, allocate. + std::vector<DWRITE_HIT_TEST_METRICS> hitTestMetrics(2); + textLayout->HitTestTextRange( + static_cast<UINT32>(startPos), + static_cast<UINT32>(rangeLength), + 0, // x + 0, // y + hitTestMetrics.data(), + static_cast<UINT32>(hitTestMetrics.size()), + &actualHitTestCount + ); + + if (actualHitTestCount == 0) { + return ret; + } + + if (hitTestMetrics.size() < actualHitTestCount) { + // Allocate enough room to return all hit-test metrics. + hitTestMetrics.resize(actualHitTestCount); + textLayout->HitTestTextRange( + static_cast<UINT32>(startPos), + static_cast<UINT32>(rangeLength), + 0, // x + 0, // y + hitTestMetrics.data(), + static_cast<UINT32>(hitTestMetrics.size()), + &actualHitTestCount + ); + } + + // Get the selection ranges behind the text. + + for (size_t i = 0; i < actualHitTestCount; ++i) { + // Store selection rectangle + const DWRITE_HIT_TEST_METRICS &htm = hitTestMetrics[i]; + const Interval selectionInterval { htm.left, htm.left + htm.width }; + ret.push_back(selectionInterval); + } + + return ret; +} + +std::unique_ptr<IScreenLineLayout> SurfaceD2D::Layout(const IScreenLine *screenLine) { + return std::make_unique<ScreenLineLayout>(screenLine); +} + +void SurfaceD2D::DrawTextCommon(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, int codePageOverride, UINT fuOptions) { + const FontDirectWrite *pfm = FontDirectWrite::Cast(font_); + if (pfm->pTextFormat && pRenderTarget && pBrush) { + // Use Unicode calls + const int codePageDraw = codePageOverride ? codePageOverride : pfm->CodePageText(mode.codePage); + const TextWide tbuf(text, codePageDraw); + + SetFontQuality(pfm->extraFontFlag); + if (fuOptions & ETO_CLIPPED) { + const D2D1_RECT_F rcClip = RectangleFromPRectangle(rc); + pRenderTarget->PushAxisAlignedClip(rcClip, D2D1_ANTIALIAS_MODE_ALIASED); + } + + // Explicitly creating a text layout appears a little faster + TextLayout pTextLayout = LayoutCreate( + tbuf.AsView(), + pfm->pTextFormat.Get(), + static_cast<FLOAT>(rc.Width()), + static_cast<FLOAT>(rc.Height())); + if (pTextLayout) { + const D2D1_POINT_2F origin = DPointFromPoint(Point(rc.left, ybase - pfm->yAscent)); + pRenderTarget->DrawTextLayout(origin, pTextLayout.Get(), pBrush.Get(), d2dDrawTextOptions); + } + + if (fuOptions & ETO_CLIPPED) { + pRenderTarget->PopAxisAlignedClip(); + } + } +} + +void SurfaceD2D::DrawTextNoClip(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, + ColourRGBA fore, ColourRGBA back) { + if (pRenderTarget) { + FillRectangleAligned(rc, back); + D2DPenColourAlpha(fore); + DrawTextCommon(rc, font_, ybase, text, 0, ETO_OPAQUE); + } +} + +void SurfaceD2D::DrawTextClipped(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, + ColourRGBA fore, ColourRGBA back) { + if (pRenderTarget) { + FillRectangleAligned(rc, back); + D2DPenColourAlpha(fore); + DrawTextCommon(rc, font_, ybase, text, 0, ETO_OPAQUE | ETO_CLIPPED); + } +} + +void SurfaceD2D::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 != ' ') { + if (pRenderTarget) { + D2DPenColourAlpha(fore); + DrawTextCommon(rc, font_, ybase, text, 0, 0); + } + return; + } + } +} + +HRESULT MeasurePositions(TextPositions &poses, const TextWide &tbuf, IDWriteTextFormat *pTextFormat) { + if (!pTextFormat) { + // Unexpected failure like no access to DirectWrite so give up. + return E_FAIL; + } + + // Initialize poses for safety. + std::fill(poses.buffer, poses.buffer + tbuf.tlen, 0.0f); + // Create a layout + TextLayout pTextLayout = LayoutCreate(tbuf.AsView(), pTextFormat); + if (!pTextLayout) { + return E_FAIL; + } + VarBuffer<DWRITE_CLUSTER_METRICS, stackBufferLength> cm(tbuf.tlen); + UINT32 count = 0; + const HRESULT hrGetCluster = pTextLayout->GetClusterMetrics(cm.buffer, tbuf.tlen, &count); + if (!SUCCEEDED(hrGetCluster)) { + return hrGetCluster; + } + const DWRITE_CLUSTER_METRICS * const clusterMetrics = cm.buffer; + // A cluster may be more than one WCHAR, such as for "ffi" which is a ligature in the Candara font + XYPOSITION position = 0.0; + int ti=0; + for (unsigned int ci=0; ci<count; ci++) { + for (unsigned int inCluster=0; inCluster<clusterMetrics[ci].length; inCluster++) { + poses.buffer[ti++] = position + clusterMetrics[ci].width * (inCluster + 1) / clusterMetrics[ci].length; + } + position += clusterMetrics[ci].width; + } + PLATFORM_ASSERT(ti == tbuf.tlen); + return S_OK; +} + +void SurfaceD2D::MeasureWidths(const Font *font_, std::string_view text, XYPOSITION *positions) { + const FontDirectWrite *pfm = FontDirectWrite::Cast(font_); + const int codePageText = pfm->CodePageText(mode.codePage); + const TextWide tbuf(text, codePageText); + TextPositions poses(tbuf.tlen); + if (FAILED(MeasurePositions(poses, tbuf, pfm->pTextFormat.Get()))) { + return; + } + if (codePageText == CpUtf8) { + // Map the widths given for UTF-16 characters back onto the UTF-8 input string + size_t i = 0; + for (int ui = 0; ui < tbuf.tlen; 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<text.length()) && (ui<tbuf.tlen); bytePos++) { + positions[i++] = poses.buffer[ui]; + } + } + const XYPOSITION lastPos = (i > 0) ? positions[i - 1] : 0.0; + while (i<text.length()) { + positions[i++] = lastPos; + } + } else if (!IsDBCSCodePage(codePageText)) { + + // One char per position + PLATFORM_ASSERT(text.length() == static_cast<size_t>(tbuf.tlen)); + for (int kk=0; kk<tbuf.tlen; kk++) { + positions[kk] = poses.buffer[kk]; + } + + } else { + + // May be one or two bytes per position + int ui = 0; + for (size_t i=0; i<text.length() && ui<tbuf.tlen;) { + positions[i] = poses.buffer[ui]; + if (DBCSIsLeadByte(codePageText, text[i])) { + positions[i+1] = poses.buffer[ui]; + i += 2; + } else { + i++; + } + + ui++; + } + } +} + +XYPOSITION SurfaceD2D::WidthText(const Font *font_, std::string_view text) { + FLOAT width = 1.0; + const FontDirectWrite *pfm = FontDirectWrite::Cast(font_); + if (pfm->pTextFormat) { + const TextWide tbuf(text, pfm->CodePageText(mode.codePage)); + // Create a layout + if (TextLayout pTextLayout = LayoutCreate(tbuf.AsView(), pfm->pTextFormat.Get())) { + DWRITE_TEXT_METRICS textMetrics; + if (SUCCEEDED(pTextLayout->GetMetrics(&textMetrics))) + width = textMetrics.widthIncludingTrailingWhitespace; + } + } + return width; +} + +void SurfaceD2D::DrawTextNoClipUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, + ColourRGBA fore, ColourRGBA back) { + if (pRenderTarget) { + FillRectangleAligned(rc, back); + D2DPenColourAlpha(fore); + DrawTextCommon(rc, font_, ybase, text, CpUtf8, ETO_OPAQUE); + } +} + +void SurfaceD2D::DrawTextClippedUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, + ColourRGBA fore, ColourRGBA back) { + if (pRenderTarget) { + FillRectangleAligned(rc, back); + D2DPenColourAlpha(fore); + DrawTextCommon(rc, font_, ybase, text, CpUtf8, ETO_OPAQUE | ETO_CLIPPED); + } +} + +void SurfaceD2D::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 != ' ') { + if (pRenderTarget) { + D2DPenColourAlpha(fore); + DrawTextCommon(rc, font_, ybase, text, CpUtf8, 0); + } + return; + } + } +} + +void SurfaceD2D::MeasureWidthsUTF8(const Font *font_, std::string_view text, XYPOSITION *positions) { + const FontDirectWrite *pfm = FontDirectWrite::Cast(font_); + const TextWide tbuf(text, CpUtf8); + TextPositions poses(tbuf.tlen); + if (FAILED(MeasurePositions(poses, tbuf, pfm->pTextFormat.Get()))) { + return; + } + // Map the widths given for UTF-16 characters back onto the UTF-8 input string + size_t i = 0; + for (int ui = 0; ui < tbuf.tlen; ui++) { + const unsigned char uch = text[i]; + const unsigned int byteCount = UTF8BytesOfLead[uch]; + if (byteCount == 4) { // Non-BMP + ui++; + PLATFORM_ASSERT(ui < tbuf.tlen); + } + for (unsigned int bytePos=0; (bytePos<byteCount) && (i<text.length()) && (ui < tbuf.tlen); bytePos++) { + positions[i++] = poses.buffer[ui]; + } + } + const XYPOSITION lastPos = (i > 0) ? positions[i - 1] : 0.0; + while (i < text.length()) { + positions[i++] = lastPos; + } +} + +XYPOSITION SurfaceD2D::WidthTextUTF8(const Font * font_, std::string_view text) { + FLOAT width = 1.0; + const FontDirectWrite *pfm = FontDirectWrite::Cast(font_); + if (pfm->pTextFormat) { + const TextWide tbuf(text, CpUtf8); + // Create a layout + if (TextLayout pTextLayout = LayoutCreate(tbuf.AsView(), pfm->pTextFormat.Get())) { + DWRITE_TEXT_METRICS textMetrics; + if (SUCCEEDED(pTextLayout->GetMetrics(&textMetrics))) + width = textMetrics.widthIncludingTrailingWhitespace; + } + } + return width; +} + +XYPOSITION SurfaceD2D::Ascent(const Font *font_) { + const FontDirectWrite *pfm = FontDirectWrite::Cast(font_); + return std::ceil(pfm->yAscent); +} + +XYPOSITION SurfaceD2D::Descent(const Font *font_) { + const FontDirectWrite *pfm = FontDirectWrite::Cast(font_); + return std::ceil(pfm->yDescent); +} + +XYPOSITION SurfaceD2D::InternalLeading(const Font *font_) { + const FontDirectWrite *pfm = FontDirectWrite::Cast(font_); + return std::floor(pfm->yInternalLeading); +} + +XYPOSITION SurfaceD2D::Height(const Font *font_) { + const FontDirectWrite *pfm = FontDirectWrite::Cast(font_); + return std::ceil(pfm->yAscent) + std::ceil(pfm->yDescent); +} + +XYPOSITION SurfaceD2D::AverageCharWidth(const Font *font_) { + FLOAT width = 1.0; + const FontDirectWrite *pfm = FontDirectWrite::Cast(font_); + if (pfm->pTextFormat) { + // Create a layout + static constexpr std::wstring_view wsvAllAlpha = L"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + if (TextLayout pTextLayout = LayoutCreate(wsvAllAlpha, pfm->pTextFormat.Get())) { + DWRITE_TEXT_METRICS textMetrics; + if (SUCCEEDED(pTextLayout->GetMetrics(&textMetrics))) + width = textMetrics.width / wsvAllAlpha.length(); + } + } + return width; +} + +void SurfaceD2D::SetClip(PRectangle rc) { + if (pRenderTarget) { + const D2D1_RECT_F rcClip = RectangleFromPRectangle(rc); + pRenderTarget->PushAxisAlignedClip(rcClip, D2D1_ANTIALIAS_MODE_ALIASED); + clipsActive++; + } +} + +void SurfaceD2D::PopClip() { + if (pRenderTarget) { + PLATFORM_ASSERT(clipsActive > 0); + pRenderTarget->PopAxisAlignedClip(); + clipsActive--; + } +} + +void SurfaceD2D::FlushCachedState() { +} + +void SurfaceD2D::FlushDrawing() { + if (pRenderTarget) { + pRenderTarget->Flush(); + } +} + +void SurfaceD2D::SetRenderingParams(std::shared_ptr<RenderingParams> renderingParams_) { + renderingParams = std::move(renderingParams_); +} + +#endif + +} + +namespace Scintilla::Internal { + +#if defined(USE_D2D) +IDWriteFactory1 *pIDWriteFactory = nullptr; +ID2D1Factory1 *pD2DFactory = nullptr; + +HRESULT CreateD3D(D3D11Device &device) noexcept { + device = nullptr; + if (!fnDCD) { + return E_FAIL; + } + + const D3D_FEATURE_LEVEL featureLevels[] = { + D3D_FEATURE_LEVEL_11_1, + D3D_FEATURE_LEVEL_11_0, + D3D_FEATURE_LEVEL_10_1, + D3D_FEATURE_LEVEL_10_0, + D3D_FEATURE_LEVEL_9_3, + D3D_FEATURE_LEVEL_9_2, + D3D_FEATURE_LEVEL_9_1 + }; + + // Create device. + // Try for a hardware device but, if that fails, fall back to the Warp software rasterizer. + ComPtr<ID3D11Device> upDevice; + HRESULT hr = S_OK; + const D3D_DRIVER_TYPE typesToTry[] = { D3D_DRIVER_TYPE_HARDWARE, D3D_DRIVER_TYPE_WARP }; + for (const D3D_DRIVER_TYPE type : typesToTry) { + hr = fnDCD(nullptr, + type, + {}, + D3D11_CREATE_DEVICE_BGRA_SUPPORT, + featureLevels, + ARRAYSIZE(featureLevels), + D3D11_SDK_VERSION, + upDevice.GetAddressOf(), + nullptr, + nullptr); + if (SUCCEEDED(hr)) + break; + } + if (FAILED(hr)) { + Platform::DebugPrintf("Failed to create D3D11 device 0x%lx\n", hr); + return hr; + } + + // Convert from D3D11 to D3D11.1 + hr = upDevice.As(&device); + if (FAILED(hr)) { + Platform::DebugPrintf("Failed to create D3D11.1 device 0x%lx\n", hr); + } + return hr; +} + +bool LoadD2D() noexcept { + static std::once_flag once; + try { + std::call_once(once, LoadD2DOnce); + } + catch (...) { + // ignore + } + return pIDWriteFactory && pD2DFactory; +} + +void ReleaseD2D() noexcept { + ReleaseUnknown(pIDWriteFactory); + ReleaseUnknown(pD2DFactory); + ReleaseLibrary(hDLLDWrite); + ReleaseLibrary(hDLLD3D); + ReleaseLibrary(hDLLD2D); +} + +HRESULT CreateDCRenderTarget(const D2D1_RENDER_TARGET_PROPERTIES *renderTargetProperties, DCRenderTarget &dcRT) noexcept { + return pD2DFactory->CreateDCRenderTarget(renderTargetProperties, dcRT.ReleaseAndGetAddressOf()); +} + +BrushSolid BrushSolidCreate(ID2D1RenderTarget *pTarget, COLORREF colour) noexcept { + BrushSolid brush; + const D2D_COLOR_F col = ColorFromColourAlpha(ColourRGBA::FromRGB(colour)); + if (FAILED(pTarget->CreateSolidColorBrush(col, brush.GetAddressOf()))) { + return {}; + } + return brush; +} + +Geometry GeometryCreate() noexcept { + Geometry geometry; + if (FAILED(pD2DFactory->CreatePathGeometry(geometry.GetAddressOf()))) { + return {}; + } + return geometry; +} + +GeometrySink GeometrySinkCreate(ID2D1PathGeometry *geometry) noexcept { + GeometrySink sink; + if (FAILED(geometry->Open(sink.GetAddressOf()))) { + return {}; + } + return sink; +} + +StrokeStyle StrokeStyleCreate(const D2D1_STROKE_STYLE_PROPERTIES &strokeStyleProperties) noexcept { + StrokeStyle strokeStyle; + const HRESULT hr = pD2DFactory->CreateStrokeStyle( + strokeStyleProperties, nullptr, 0, strokeStyle.GetAddressOf()); + if (FAILED(hr)) { + return {}; + } + return strokeStyle; +} + +TextLayout LayoutCreate(std::wstring_view wsv, IDWriteTextFormat *pTextFormat, FLOAT maxWidth, FLOAT maxHeight) noexcept { + TextLayout layout; + const HRESULT hr = pIDWriteFactory->CreateTextLayout(wsv.data(), static_cast<UINT32>(wsv.length()), + pTextFormat, maxWidth, maxHeight, layout.GetAddressOf()); + if (FAILED(hr)) { + return {}; + } + return layout; +} + +#endif + +std::shared_ptr<Font> Font::Allocate(const FontParameters &fp) { +#if defined(USE_D2D) + if (fp.technology != Technology::Default) { + return std::make_shared<FontDirectWrite>(fp); + } +#endif + return FontGDI_Allocate(fp); +} + +std::unique_ptr<Surface> Surface::Allocate([[maybe_unused]] Technology technology) { +#if defined(USE_D2D) + if (technology != Technology::Default) { + return std::make_unique<SurfaceD2D>(); + } +#endif + return SurfaceGDI_Allocate(); +} + +} diff --git a/win32/SurfaceD2D.h b/win32/SurfaceD2D.h new file mode 100644 index 000000000..13da459cc --- /dev/null +++ b/win32/SurfaceD2D.h @@ -0,0 +1,50 @@ +// Scintilla source code edit control +/** @file SurfaceD2D.h + ** Definitions for drawing to Direct2D on Windows. + **/ +// Copyright 1998-2011 by Neil Hodgson <neilh@scintilla.org> +// The License.txt file describes the conditions under which this software may be distributed. + +#ifndef SURFACED2D_H +#define SURFACED2D_H + +namespace Scintilla::Internal { + +extern bool LoadD2D() noexcept; +extern void ReleaseD2D() noexcept; +extern ID2D1Factory1 *pD2DFactory; +extern IDWriteFactory1 *pIDWriteFactory; + +using DCRenderTarget = ComPtr<ID2D1DCRenderTarget>; + +using D3D11Device = ComPtr<ID3D11Device1>; + +HRESULT CreateDCRenderTarget(const D2D1_RENDER_TARGET_PROPERTIES *renderTargetProperties, DCRenderTarget &dcRT) noexcept; +extern HRESULT CreateD3D(D3D11Device &device) noexcept; + +using WriteRenderingParams = ComPtr<IDWriteRenderingParams1>; + +struct RenderingParams { + WriteRenderingParams defaultRenderingParams; + WriteRenderingParams customRenderingParams; +}; + +struct ISetRenderingParams { + virtual void SetRenderingParams(std::shared_ptr<RenderingParams> renderingParams_) = 0; +}; + +using BrushSolid = ComPtr<ID2D1SolidColorBrush>; +using Geometry = ComPtr<ID2D1PathGeometry>; +using GeometrySink = ComPtr<ID2D1GeometrySink>; +using StrokeStyle = ComPtr<ID2D1StrokeStyle>; +using TextLayout = ComPtr<IDWriteTextLayout>; + +BrushSolid BrushSolidCreate(ID2D1RenderTarget *pTarget, COLORREF colour) noexcept; +Geometry GeometryCreate() noexcept; +GeometrySink GeometrySinkCreate(ID2D1PathGeometry *geometry) noexcept; +StrokeStyle StrokeStyleCreate(const D2D1_STROKE_STYLE_PROPERTIES &strokeStyleProperties) noexcept; +TextLayout LayoutCreate(std::wstring_view wsv, IDWriteTextFormat *pTextFormat, FLOAT maxWidth=10000.0F, FLOAT maxHeight=1000.0F) noexcept; + +} + +#endif 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>(); +} + +} diff --git a/win32/SurfaceGDI.h b/win32/SurfaceGDI.h new file mode 100644 index 000000000..990b052fc --- /dev/null +++ b/win32/SurfaceGDI.h @@ -0,0 +1,18 @@ +// Scintilla source code edit control +/** @file SurfaceGDI.h + ** Definitions for drawing to GDI on Windows. + **/ +// Copyright 2025 by Neil Hodgson <neilh@scintilla.org> +// The License.txt file describes the conditions under which this software may be distributed. + +#ifndef SURFACEGDI_H +#define SURFACEGDI_H + +namespace Scintilla::Internal { + +std::shared_ptr<Font> FontGDI_Allocate(const FontParameters &fp); +std::unique_ptr<Surface> SurfaceGDI_Allocate(); + +} + +#endif diff --git a/win32/WinTypes.h b/win32/WinTypes.h index c0d6c558d..f2bbfd5f9 100644 --- a/win32/WinTypes.h +++ b/win32/WinTypes.h @@ -52,6 +52,13 @@ inline T DLLFunction(HMODULE hModule, LPCSTR lpProcName) noexcept { return fp; } +inline void ReleaseLibrary(HMODULE &hLib) noexcept { + if (hLib) { + FreeLibrary(hLib); + hLib = {}; + } +} + } #endif diff --git a/win32/deps.mak b/win32/deps.mak index 8c36e8ddb..2be674777 100644 --- a/win32/deps.mak +++ b/win32/deps.mak @@ -3,6 +3,19 @@ $(DIR_O)/HanjaDic.o: \ HanjaDic.cxx \ WinTypes.h \ HanjaDic.h +$(DIR_O)/ListBox.o: \ + ListBox.cxx \ + ../include/ScintillaTypes.h \ + ../src/Debugging.h \ + ../src/Geometry.h \ + ../src/Platform.h \ + ../src/XPM.h \ + ../src/UniConversion.h \ + ../src/DBCS.h \ + WinTypes.h \ + PlatWin.h \ + ListBox.h \ + SurfaceD2D.h $(DIR_O)/PlatWin.o: \ PlatWin.cxx \ ../include/ScintillaTypes.h \ @@ -13,7 +26,9 @@ $(DIR_O)/PlatWin.o: \ ../src/UniConversion.h \ ../src/DBCS.h \ WinTypes.h \ - PlatWin.h + PlatWin.h \ + ListBox.h \ + SurfaceD2D.h $(DIR_O)/ScintillaDLL.o: \ ScintillaDLL.cxx \ ../include/ScintillaTypes.h \ @@ -60,8 +75,34 @@ $(DIR_O)/ScintillaWin.o: \ ../src/ScintillaBase.h \ WinTypes.h \ PlatWin.h \ + SurfaceD2D.h \ HanjaDic.h \ ScintillaWin.h +$(DIR_O)/SurfaceD2D.o: \ + SurfaceD2D.cxx \ + ../include/ScintillaTypes.h \ + ../src/Debugging.h \ + ../src/Geometry.h \ + ../src/Platform.h \ + ../src/XPM.h \ + ../src/UniConversion.h \ + ../src/DBCS.h \ + WinTypes.h \ + PlatWin.h \ + SurfaceGDI.h \ + SurfaceD2D.h +$(DIR_O)/SurfaceGDI.o: \ + SurfaceGDI.cxx \ + ../include/ScintillaTypes.h \ + ../src/Debugging.h \ + ../src/Geometry.h \ + ../src/Platform.h \ + ../src/XPM.h \ + ../src/UniConversion.h \ + ../src/DBCS.h \ + WinTypes.h \ + PlatWin.h \ + SurfaceGDI.h $(DIR_O)/AutoComplete.o: \ ../src/AutoComplete.cxx \ ../include/ScintillaTypes.h \ diff --git a/win32/makefile b/win32/makefile index 1760537c5..72634889f 100644 --- a/win32/makefile +++ b/win32/makefile @@ -118,6 +118,9 @@ COMPONENT_OBJS = \ $(SRC_OBJS) \ $(DIR_O)/HanjaDic.o \ $(DIR_O)/PlatWin.o \ + $(DIR_O)/ListBox.o \ + $(DIR_O)/SurfaceGDI.o \ + $(DIR_O)/SurfaceD2D.o \ $(DIR_O)/ScintillaBase.o \ $(DIR_O)/ScintillaWin.o diff --git a/win32/nmdeps.mak b/win32/nmdeps.mak index 961aba5ee..c107fed6c 100644 --- a/win32/nmdeps.mak +++ b/win32/nmdeps.mak @@ -3,6 +3,19 @@ $(DIR_O)/HanjaDic.obj: \ HanjaDic.cxx \ WinTypes.h \ HanjaDic.h +$(DIR_O)/ListBox.obj: \ + ListBox.cxx \ + ../include/ScintillaTypes.h \ + ../src/Debugging.h \ + ../src/Geometry.h \ + ../src/Platform.h \ + ../src/XPM.h \ + ../src/UniConversion.h \ + ../src/DBCS.h \ + WinTypes.h \ + PlatWin.h \ + ListBox.h \ + SurfaceD2D.h $(DIR_O)/PlatWin.obj: \ PlatWin.cxx \ ../include/ScintillaTypes.h \ @@ -13,7 +26,9 @@ $(DIR_O)/PlatWin.obj: \ ../src/UniConversion.h \ ../src/DBCS.h \ WinTypes.h \ - PlatWin.h + PlatWin.h \ + ListBox.h \ + SurfaceD2D.h $(DIR_O)/ScintillaDLL.obj: \ ScintillaDLL.cxx \ ../include/ScintillaTypes.h \ @@ -60,8 +75,34 @@ $(DIR_O)/ScintillaWin.obj: \ ../src/ScintillaBase.h \ WinTypes.h \ PlatWin.h \ + SurfaceD2D.h \ HanjaDic.h \ ScintillaWin.h +$(DIR_O)/SurfaceD2D.obj: \ + SurfaceD2D.cxx \ + ../include/ScintillaTypes.h \ + ../src/Debugging.h \ + ../src/Geometry.h \ + ../src/Platform.h \ + ../src/XPM.h \ + ../src/UniConversion.h \ + ../src/DBCS.h \ + WinTypes.h \ + PlatWin.h \ + SurfaceGDI.h \ + SurfaceD2D.h +$(DIR_O)/SurfaceGDI.obj: \ + SurfaceGDI.cxx \ + ../include/ScintillaTypes.h \ + ../src/Debugging.h \ + ../src/Geometry.h \ + ../src/Platform.h \ + ../src/XPM.h \ + ../src/UniConversion.h \ + ../src/DBCS.h \ + WinTypes.h \ + PlatWin.h \ + SurfaceGDI.h $(DIR_O)/AutoComplete.obj: \ ../src/AutoComplete.cxx \ ../include/ScintillaTypes.h \ diff --git a/win32/scintilla.mak b/win32/scintilla.mak index 5034c8d0d..f2cf5c93d 100644 --- a/win32/scintilla.mak +++ b/win32/scintilla.mak @@ -113,6 +113,9 @@ SRC_OBJS=\ COMPONENT_OBJS = \ $(DIR_O)\HanjaDic.obj \ $(DIR_O)\PlatWin.obj \ + $(DIR_O)\ListBox.obj \ + $(DIR_O)\SurfaceGDI.obj \ + $(DIR_O)\SurfaceD2D.obj \ $(DIR_O)\ScintillaBase.obj \ $(DIR_O)\ScintillaWin.obj \ $(SRC_OBJS) |