D2TL_ClusterMetrics function

Requires: Direct2D.lg32

Purpose

Obtains an array of cluster metrics with the information for each cluster (word) and returns the number of array entries.

Syntax

ClusterCount = D2TL_ClusterMetrics(D2Layout, ClusterMetrics())

HitTestCount, ClusterCount: integer
D2Layout: Object
ClusterMetrics(): array of DWRITECLUSTERMETRICS type

Description

D2TL_ClusterMetrics obtains an array with logical properties and measurements of each glyph cluster (word). The function returns the number of array entries actually obtained. When the function returns 0 the array is not filled and not (re-)dimensioned, otherwise the array is dimensioned to the number of entries required to store the hit-test metrics information. The array is dimmed from 0 .. ClusterCount - 1.

One of the main usages is to implement caret movement from word to word, see example.

Type DWRITECLUSTERMETRICS

- Float width

- Card length

canWrapLineAfter As Bits 1

isWhitespace As Bits 1

isNewline As Bits 1

isSoftHyphen As Bits 1

isRightToLeft As Bits 1

padding As Bits 11

EndType

Example

'

' D2TL_ClusterMetrics  (dpi-aware)

'

' Move arrow keys Left/Right to move caret from word to word.

'

$Library "direct2d"

$Library "gfawinx"

DpiAwareness()

OpenW 1, 0, 0, 300, 300, 48

FormScaleForDpi(Me)

Global Object RT1

Set RT1 = D2GetRT()

' Build a Text Layout object for a given text

Global Txt1$ = "Hello DirectX" #10 "This is GB" #10 "(GFABASIC 32)"

D2SetFont "Gabriola", 18  ' set a base font for TextLayout

' Obtain TextLayout for text with current font.

' If Txt1 or dimension changes recreate the TextLayout object.

Global oTL1 As Object

Set oTL1 = D2Layout(Txt1$, 100, 60)

' Set font data for text ranges

D2DefLayout oTL1, 0, 5, D2TL_FONTNAME, "Courier New"

D2DefLayout oTL1, 0, 5, D2TL_UNDERLINE, True

D2DefLayout oTL1, 0, 5, D2TL_FONTSIZE, 12

Global charpos As Long, caretX!, caretY!, caretH!

UpdateCaretPos

Do

Sleep

Until Me Is Nothing

 

Sub Win_1_Paint

~HideCaret(0)

D2BeginDraw RT1, D2C_WhiteSmoke

D2TextLayout 10, 10, oTL1 //, D2T_CENTER

D2EndDraw    ' resets current render target to nothing

~ShowCaret(0)

EndSub

 

Sub Win_1_KeyDown(Code&, Shift&)

Local clusterMetrics() As DWRITE_CLUSTER_METRICS, cluster As Long, clusterpos As Long

Local clusterCnt As Long, clusterlength As Long, oldcharpos As Long

Switch Code&

Case VK_RIGHT

clusterCnt = D2TL_ClusterMetrics(oTL1, clusterMetrics())

oldcharpos = charpos, charpos = 0

While cluster < clusterCnt

clusterlength = clusterMetrics(cluster).length

If clusterpos + clusterlength > oldcharpos && clusterMetrics(cluster).canWrapLineAfter

charpos = clusterpos + clusterlength

Exit Do

EndIf

clusterpos += clusterlength

charpos = clusterpos

cluster++

Wend

UpdateCaretPos

Win_1_Paint

Case VK_LEFT

clusterCnt = D2TL_ClusterMetrics(oTL1, clusterMetrics())

oldcharpos = charpos, charpos = 0

While cluster < clusterCnt

clusterpos += clusterMetrics(cluster).length

If clusterMetrics(cluster).canWrapLineAfter

Exit Do If clusterpos >= oldcharpos

charpos = clusterpos

EndIf

cluster++

Wend

UpdateCaretPos

Win_1_Paint

EndSwitch

EndSub

 

Proc UpdateCaretPos()

Dim caretMetrics As DWRITE_HIT_TEST_METRICS

D2TL_HitTestTextPos oTL1, charpos, caretX!, caretY!, caretMetrics

caretH! = D2DIPsToPixel(caretMetrics.height)

~CreateCaret(Me.hWnd, Null, 2, caretH!)

~SetCaretPos(D2DIPsToPixel(10 + caretX!), D2DIPsToPixel(10 + caretY!))

~ShowCaret(Me.hWnd)

EndProc

 

Sub Win_1_ReSize

D2ResizeRT RT1, _X, _Y

UpdateCaretPos

 

Sub Win_1_MessageProc(hWnd%, Mess%, wParam%, lParam%, retval%, ValidRet?)

If Mess == WM_DPICHANGED

If FormScaleForDpi(Me, wParam%, lParam%)

UpdateCaretPos

D2SetDpiRT RT1, LoWord(wParam%)    ' Set DPI of the RT of Win1

EndIf

retval = 0, ValidRet? = True

EndIf

EndSub

 

Function FormScaleForDpi(ByVal frm As Form, Optional newdpi As Int, Optional lParam As Int) As Bool

Local i As Int, c As Control, rc As RECT, prevDpi As Int, newrc As Pointer To RECT, _

tb As Tab, w As Int, h As Int

With frm

' Set initial newdpi for the form and the scale factors for a LoadForm

If .WhatsThisHelpID == 0

If Exist(":{" + .Name)                    ' a Form-Editor form

.WhatsThisHelpID = WinDpi(0)            ' initial dpi is system newdpi

.ScaleWidth = .ScaleWidth * 96 / .WhatsThisHelpID   ' set initial scale factors

.ScaleHeight = .ScaleHeight * 96 / .WhatsThisHelpID

Else                                      ' created by OpenW or Form command

.WhatsThisHelpID = 96                   ' coord space is 96

EndIf

EndIf

prevDpi = .WhatsThisHelpID

newdpi = LoWord(newdpi)

If newdpi == 0 Then newdpi = WinDpi(.hWnd)

Exit Func If newdpi == prevDpi              ' don't continue if equal

' Size the form, except if it is a child window

If !(GetWindowLong(.hWnd, GWL_STYLE) %& WS_CHILD)

If lParam                                 ' size to the OS suggested size

Pointer newrc = lParam                  ' ptr to RECT in lParam

SizeW .hWnd, newrc.Right - newrc.Left, newrc.Bottom - newrc.Top

Else                                      ' scale based on the current client size

GetClientSize .hWnd, w, h               ' can't use _X _Y, they are for ME only

AdjustW .hWnd, Scale(w, newdpi, prevDpi), Scale(h, newdpi, prevDpi)

EndIf

EndIf

.FontSize = .FontSize * newdpi / prevDpi

.ScaleWidth = .ScaleWidth * prevDpi / newdpi

.ScaleHeight = .ScaleHeight * prevDpi / newdpi

' Scale position and size of the Ocx windowed controls

For Each c In frm.Controls

If !(TypeOf(c) Is ImageList || TypeOf(c) Is Timer || _

TypeOf(c) Is CommDlg || TypeOf(c) Is TrayIcon' not the windowless OCX-es

' First set fontsize, but not all OCX-es have a Font property

If !(TypeOf(c) Is ProgressBar || TypeOf(c) Is Slider || TypeOf(c) Is Image || TypeOf(c) Is RichEdit)

c.fontsize = c.fontsize * newdpi / prevDpi

ElseIf TypeOf(c) Is RichEdit            ' Requires LoadRichEdit50W!

SendMessage c.hwnd, WM_DPICHANGED, MakeLong(newdpi, newdpi), 0

EndIf

' Set new position and size for OCX-controls

GetWinPos(c.hwnd, rc)                   ' relative to parent

~SetWindowPos(c.hwnd, 0, _

Scale(rc.Left, newdpi, prevDpi), Scale(rc.Top, newdpi, prevDpi), _

Scale(rc.Right - rc.Left, newdpi, prevDpi), _

Scale(rc.Bottom - rc.Top, newdpi, prevDpi),  SWP_NOZORDER) ' new pos and size

If TypeOf(c) Is Form                    ' Scale OCX-es of a child Form Ocx

c.WhatsThisHelpID = prevDpi

~FormScaleForDpi(c, newdpi)

ElseIf TypeOf(c) Is TabStrip            ' Scale TabStrip's attached forms

For Each tb In c.Tabs

If TypeOf(tb.Ocx) Is Form

tb.Ocx.WhatsThisHelpID = prevDpi

~FormScaleForDpi(tb.Ocx, newdpi)

EndIf

Next

EndIf

EndIf

Next

.WhatsThisHelpID = newdpi     ' store new newdpi

EndWith

FormScaleForDpi = True

EndFunc

Remarks

D2TLClusterMetrics is a wrapper for the IDWriteTextLayout::GetClusterMetrics method. The DWRITECLUSTERMETRICS type is described at https://docs.microsoft.com/en-us/windows/win32/api/dwrite/ns-dwrite-dwriteclustermetrics.

See Also

D2TL_HitTestPoint, D2TL_HitTestTextPos, D2TL_HitTestTextRange, D2TL_LineMetrics, D2TL_Metrics, D2Layout, D2TextLayout, D2DefLayout, D2RLayout

{Created by Sjouke Hamstra; Last updated: 03/03/2022 by James Gaite}