Tk Source Code

View Ticket
Login
Ticket UUID: 18f35ed566d96f9d3d55e99e0af3ef8f10faf7
Title: [text]: "display lineend" lands inside a multi-byte character, so [get] returns half a character
Type: Bug Version: 8.6.18, 9.0.5, 9.1.0
Submitter: serhiy.storchaka Created on: 2026-10-18 12:57:39
Subsystem: 18. [text] Assigned To: jan.nijtmans
Priority: 5 Medium Severity: Minor
Status: Closed Last Modified: 2026-10-22 17:06:15
Resolution: Fixed Closed By: jan.nijtmans
    Closed on: 2026-10-22 17:06:15
Description:

In a text widget, the index display lineend lands in the middle of a character when the display line ends with a character that takes more than one byte in UTF-8:

pack [text .t -width 1 -wrap char]
.t insert end "aäb"
update
.t get "1.1 display linestart" "1.1 display lineend"

returns à (U+00C3, the first byte of the UTF-8 encoding of ä), instead of ä. [.t index "1.1 display lineend"] says 1.2, and [.t get 1.1 1.2] correctly returns ä. [.t dump -text] gives à as well.

TkTextFindDisplayLineEnd() in generic/tkTextDisp.c computes the end as the start of the next display line minus one byte (TkTextIndexBackBytes(textPtr, &nextLineStart, 1, indexPtr)). That is right when the display line ends with a space or a newline, as it usually does, but not when it ends inside a word with a multi-byte character, as with -wrap char or a word longer than the width.

This is the cause of Tcl ticket 1943546fff (2008): a script stepping through an unmapped -wrap word text by display lines got the lone byte from [.t get], and [puts] failed with "invalid argument". An unmapped text widget was laid out at 1x1 pixels, so every character was a display line of its own. Since [7d8d10e4a9] (now on all branches, not yet in a release) the requested size is used, which hides that script, but not the bug.

Seen with 8.6.18, 9.0.5 and 9.1.0.

User Comments: jan.nijtmans added on 2026-10-22 17:06:15:

Fixed in [1a277e8621|trunk], core-9-0-branch and core-8-6-branch

Many thanks!


serhiy.storchaka added on 2026-10-18 13:06:02:

Fix in branch text-display-lineend-multibyte: move back one character instead of one byte from the start of the next display line. Note that the end of a display line is the index of its last character, so with the example [.t index "1.1 display lineend"] is now 1.1, and [.t get "1.1 display linestart" "1.1 display lineend"] returns an empty string, as for ASCII text; [.t get "1.1 display linestart" "1.1 display lineend +1c"] returns ä.