From 6eb17d212faf798cf9cd89c85467d8f8893ca614 Mon Sep 17 00:00:00 2001 From: "jan.nijtmans" Date: Wed, 13 Mar 2024 22:13:12 +0000 Subject: More progress, backporting bugfix from Tk 9.0 --- generic/tkInt.h | 22 +++++++++---- macosx/tkMacOSXFont.c | 86 ++++++++++++++++++++++++++++++++++++--------------- tests/cluster.test | 32 +++++++++---------- 3 files changed, 93 insertions(+), 47 deletions(-) diff --git a/generic/tkInt.h b/generic/tkInt.h index 6560dc3..8df2375 100644 --- a/generic/tkInt.h +++ b/generic/tkInt.h @@ -83,17 +83,27 @@ # define TCL_COMBINE 0 #endif +#undef TclNumUtfChars +#undef TclUtfAtIndex #undef Tcl_NumUtfChars #undef Tcl_GetCharLength #undef Tcl_UtfAtIndex #if TCL_MAJOR_VERSION > 8 -#define Tcl_NumUtfChars \ - (tclStubsPtr->tcl_NumUtfChars) /* 669 */ -#define Tcl_GetCharLength \ - (tclStubsPtr->tcl_GetCharLength) /* 670 */ -#define Tcl_UtfAtIndex \ - (tclStubsPtr->tcl_UtfAtIndex) /* 671 */ +# define TclNumUtfChars \ + (tclStubsPtr->tclNumUtfChars) /* 312 */ +# define TclUtfAtIndex \ + (tclStubsPtr->tclUtfAtIndex) /* 325 */ +# define Tcl_NumUtfChars \ + (tclStubsPtr->tcl_NumUtfChars) /* 669 */ +# define Tcl_GetCharLength \ + (tclStubsPtr->tcl_GetCharLength) /* 670 */ +# define Tcl_UtfAtIndex \ + (tclStubsPtr->tcl_UtfAtIndex) /* 671 */ #else +# define TclNumUtfChars \ + (tclStubsPtr->tcl_NumUtfChars) /* 312 */ +# define TclUtfAtIndex \ + (tclStubsPtr->tcl_UtfAtIndex) /* 325 */ # define Tcl_NumUtfChars (((&tclStubsPtr->tcl_PkgProvideEx)[631]) ? \ ((Tcl_Size (*)(const char *, Tcl_Size))(void *)((&tclStubsPtr->tcl_PkgProvideEx)[669])) \ : (tclStubsPtr->tcl_NumUtfChars) /* 312 */) diff --git a/macosx/tkMacOSXFont.c b/macosx/tkMacOSXFont.c index fb1e403..32a4855 100644 --- a/macosx/tkMacOSXFont.c +++ b/macosx/tkMacOSXFont.c @@ -451,31 +451,49 @@ startOfClusterObjCmd( { TKNSString *S; const char *stringArg; - Tcl_Size numBytes, index; + Tcl_Size len, idx; if ((unsigned)(objc - 3) > 1) { Tcl_WrongNumArgs(interp, 1 , objv, "str start ?locale?"); return TCL_ERROR; } - stringArg = Tcl_GetStringFromObj(objv[1], &numBytes); + stringArg = Tcl_GetStringFromObj(objv[1], &len); if (stringArg == NULL) { return TCL_ERROR; } - S = [[TKNSString alloc] initWithTclUtfBytes:stringArg length:numBytes]; - if (TkGetIntForIndex(objv[2], [S length] - 1, 0, &index) != TCL_OK) { + Tcl_Size ulen = Tcl_GetCharLength(objv[1]); + S = [[TKNSString alloc] initWithTclUtfBytes:stringArg length:len]; + len = [S length]; + if (TkGetIntForIndex(objv[2], ulen - 1, 0, &idx) != TCL_OK) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "bad index \"%s\": must be integer?[+-]integer?, end?[+-]integer?, or \"\"", Tcl_GetString(objv[2]))); Tcl_SetErrorCode(interp, "TK", "VALUE", "INDEX", NULL); return TCL_ERROR; } - if (index >= 0) { - if ((size_t)index >= [S length]) { - index = (Tcl_Size)[S length]; + if (idx > 0 && len != ulen) { + /* The string contains codepoints > \uFFFF. Determine UTF-16 index */ + Tcl_Size newIdx = 0; + for (Tcl_Size i = 0; i < idx; i++) { + newIdx += 1 + (((newIdx < (Tcl_Size)len-1) && ([S characterAtIndex:newIdx]&0xFC00) == 0xD800) && (([S characterAtIndex:newIdx+1]&0xFC00) == 0xDC00)); + } + idx = newIdx; + } + if (idx >= 0) { + if (idx >= len) { + idx = len; } else { - NSRange range = [S rangeOfComposedCharacterSequenceAtIndex:index]; - index = range.location; + NSRange range = [S rangeOfComposedCharacterSequenceAtIndex:idx]; + idx = range.location; + } + if (idx > 0 && len != ulen) { + /* The string contains codepoints > \uFFFF. Determine UTF-32 index */ + Tcl_Size newIdx = 1; + for (Tcl_Size i = 1; i < idx; i++) { + if ((([S characterAtIndex:i-1]&0xFC00) != 0xD800) || (([S characterAtIndex:i]&0xFC00) != 0xDC00)) newIdx++; + } + idx = newIdx; } - Tcl_SetObjResult(interp, TkNewIndexObj(index)); + Tcl_SetObjResult(interp, TkNewIndexObj(idx)); } return TCL_OK; } @@ -489,32 +507,50 @@ endOfClusterObjCmd( { TKNSString *S; char *stringArg; - Tcl_Size index, numBytes; + Tcl_Size idx, len; if ((unsigned)(objc - 3) > 1) { Tcl_WrongNumArgs(interp, 1 , objv, "str start ?locale?"); return TCL_ERROR; } - stringArg = Tcl_GetStringFromObj(objv[1], &numBytes); + stringArg = Tcl_GetStringFromObj(objv[1], &len); if (stringArg == NULL) { return TCL_ERROR; } - S = [[TKNSString alloc] initWithTclUtfBytes:stringArg length:numBytes]; - if (TkGetIntForIndex(objv[2], [S length] - 1, 0, &index) != TCL_OK) { + Tcl_Size ulen = Tcl_GetCharLength(objv[1]); + S = [[TKNSString alloc] initWithTclUtfBytes:stringArg length:len]; + len = [S length]; + if (TkGetIntForIndex(objv[2], ulen - 1, 0, &idx) != TCL_OK) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "bad index \"%s\": must be integer?[+-]integer?, end?[+-]integer?, or \"\"", Tcl_GetString(objv[2]))); Tcl_SetErrorCode(interp, "TK", "VALUE", "INDEX", NULL); return TCL_ERROR; } - if ((size_t)index + 1 <= [S length]) { - if (index < 0) { - index = 0; + if (idx > 0 && len != ulen) { + /* The string contains codepoints > \uFFFF. Determine UTF-16 index */ + Tcl_Size newIdx = 0; + for (Tcl_Size i = 0; i < idx; i++) { + newIdx += 1 + (((newIdx < len-1) && ([S characterAtIndex:newIdx]&0xFC00) == 0xD800) && (([S characterAtIndex:newIdx+1]&0xFC00) == 0xDC00)); + } + idx = newIdx; + } + if (idx + 1 <= len) { + if (idx < 0) { + idx = 0; } else { - NSRange range = [S rangeOfComposedCharacterSequenceAtIndex:index]; - index = range.location + range.length; + NSRange range = [S rangeOfComposedCharacterSequenceAtIndex:idx]; + idx = range.location + range.length; + if (idx > 0 && len != ulen) { + /* The string contains codepoints > \uFFFF. Determine UTF-32 index */ + Tcl_Size newIdx = 1; + for (Tcl_Size i = 1; i < idx; i++) { + if ((([S characterAtIndex:i-1]&0xFC00) != 0xD800) || (([S characterAtIndex:i]&0xFC00) != 0xDC00)) newIdx++; + } + idx = newIdx; + } } - Tcl_SetObjResult(interp, TkNewIndexObj(index)); + Tcl_SetObjResult(interp, TkNewIndexObj(idx)); } return TCL_OK; } @@ -1030,8 +1066,8 @@ TkpMeasureCharsInContext( attributes:fontPtr->nsAttributes]; typesetter = CTTypesetterCreateWithAttributedString( (CFAttributedStringRef)attributedString); - start = Tcl_NumUtfChars(source, rangeStart); - len = Tcl_NumUtfChars(source + rangeStart, rangeLength); + start = TclNumUtfChars(source, rangeStart); + len = TclNumUtfChars(source + rangeStart, rangeLength); if (start > 0) { range.length = start; line = CTTypesetterCreateLine(typesetter, range); @@ -1132,7 +1168,7 @@ TkpMeasureCharsInContext( [attributedString release]; [string release]; length = ceil(width - offset); - fit = (Tcl_UtfAtIndex(source, index) - source) - rangeStart; + fit = (TclUtfAtIndex(source, index) - source) - rangeStart; done: #ifdef TK_MAC_DEBUG_FONTS TkMacOSXDbgMsg("measure: source=\"%s\" range=\"%.*s\" maxLength=%d " @@ -1331,8 +1367,8 @@ TkpDrawAngledCharsInContext( -textX, -textY); } CGContextConcatCTM(context, t); - start = Tcl_NumUtfChars(source, rangeStart); - length = Tcl_NumUtfChars(source, rangeStart + rangeLength) - start; + start = TclNumUtfChars(source, rangeStart); + length = TclNumUtfChars(source, rangeStart + rangeLength) - start; line = CTTypesetterCreateLine(typesetter, CFRangeMake(start, length)); if (start > 0) { diff --git a/tests/cluster.test b/tests/cluster.test index 998759e..be889c3 100644 --- a/tests/cluster.test +++ b/tests/cluster.test @@ -15,53 +15,53 @@ testConstraint needsICU [expr {[catch {info body ::tk::startOfCluster}]}] test cluster-1.0 {::tk::startOfCluster} -body { - ::tk::startOfCluster 🤡 -1 + ::tk::startOfCluster é -1 } -result {} test cluster-1.1 {::tk::startOfCluster} -body { - ::tk::startOfCluster 🤡 0 + ::tk::startOfCluster é 0 } -result 0 test cluster-1.2 {::tk::startOfCluster} -constraints needsICU -body { - ::tk::startOfCluster 🤡 1 + ::tk::startOfCluster é 1 } -result 0 test cluster-1.3 {::tk::startOfCluster} -constraints needsICU -body { - ::tk::startOfCluster 🤡 2 + ::tk::startOfCluster é 2 } -result 2 test cluster-1.4 {::tk::startOfCluster} -constraints needsICU -body { - ::tk::startOfCluster 🤡 3 + ::tk::startOfCluster é 3 } -result 2 test cluster-1.5 {::tk::startOfCluster} -constraints needsICU -body { - ::tk::startOfCluster 🤡 end + ::tk::startOfCluster é end } -result 0 test cluster-1.6 {::tk::startOfCluster} -body { - ::tk::startOfCluster 🤡 {} + ::tk::startOfCluster é {} } -result {} test cluster-1.7 {::tk::startOfCluster} -constraints needsICU -body { - ::tk::startOfCluster 🤡 end-1 + ::tk::startOfCluster é end-1 } -result 0 test cluster-2.0 {::tk::endOfCluster} -body { - ::tk::endOfCluster 🤡 -1 + ::tk::endOfCluster é -1 } -result 0 test cluster-2.1 {::tk::endOfCluster} -constraints needsICU -body { - ::tk::endOfCluster 🤡 0 + ::tk::endOfCluster é 0 } -result 2 test cluster-2.2 {::tk::endOfCluster} -constraints needsICU -body { - ::tk::endOfCluster 🤡 1 + ::tk::endOfCluster é 1 } -result 2 test cluster-2.3 {::tk::endOfCluster} -body { - ::tk::endOfCluster 🤡 2 + ::tk::endOfCluster é 2 } -result {} test cluster-2.4 {::tk::endOfCluster} -body { - ::tk::endOfCluster 🤡 3 + ::tk::endOfCluster é 3 } -result {} test cluster-2.5 {::tk::endOfCluster} -constraints needsICU -body { - ::tk::endOfCluster 🤡 end + ::tk::endOfCluster é end } -result 2 test cluster-2.6 {::tk::endOfCluster} -body { - ::tk::endOfCluster 🤡 {} + ::tk::endOfCluster é {} } -result 0 test cluster-2.7 {::tk::endOfCluster} -constraints needsICU -body { - ::tk::endOfCluster 🤡 end-1 + ::tk::endOfCluster é end-1 } -result 2 test cluster-3.0 {::tk::endOfWord} -body { -- cgit v0.12