From 93e28a245412936e006f8a159bedaf9e1fbe1b7d Mon Sep 17 00:00:00 2001 From: "K. Adam Christensen" Date: Sun, 12 Jul 2026 16:50:54 -0700 Subject: [PATCH] Fix overcounting letter spacing in calculating final layout When calculating the width of a line, the width excludes the trailing letter spacing value: ```c measuredWidth = CLAY__MAX(lineWidth, measuredWidth) - config->letterSpacing; ``` But when the final layout is being calculated, the letter spacing is added to the line width of the text. So when it's time to render, the line is actually longer than the intial max line, so it gets broken up, even though it should fit. https://github.com/pope/clay-letterspacing-poc is a little bit of code to test a before and after. --- clay.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/clay.h b/clay.h index 7d9cf4c..6be1c92 100644 --- a/clay.h +++ b/clay.h @@ -2604,17 +2604,17 @@ void Clay__CalculateFinalLayout(float deltaTime, bool useStoredBoundingBoxes, bo } Clay__MeasuredWord *measuredWord = Clay__MeasuredWordArray_Get(&context->measuredWords, wordIndex); // Only word on the line is too large, just render it anyway - if (lineLengthChars == 0 && lineWidth + measuredWord->width > containerElement->dimensions.width) { - Clay__WrappedTextLineArray_Add(&context->wrappedTextLines, CLAY__INIT(Clay__WrappedTextLine) { { measuredWord->width, lineHeight }, { .length = measuredWord->length, .chars = &textElementData->text.chars[measuredWord->startOffset] } }); + if (lineLengthChars == 0 && lineWidth + measuredWord->width - containerElement->textConfig.letterSpacing > containerElement->dimensions.width) { + Clay__WrappedTextLineArray_Add(&context->wrappedTextLines, CLAY__INIT(Clay__WrappedTextLine) { { measuredWord->width - containerElement->textConfig.letterSpacing, lineHeight }, { .length = measuredWord->length, .chars = &textElementData->text.chars[measuredWord->startOffset] } }); textElementData->wrappedLines.length++; wordIndex = measuredWord->next; lineStartOffset = measuredWord->startOffset + measuredWord->length; } // measuredWord->length == 0 means a newline character - else if (measuredWord->length == 0 || lineWidth + measuredWord->width > containerElement->dimensions.width) { + else if (measuredWord->length == 0 || lineWidth + measuredWord->width - containerElement->textConfig.letterSpacing > containerElement->dimensions.width) { // Wrapped text lines list has overflowed, just render out the line bool finalCharIsSpace = textElementData->text.chars[CLAY__MAX(lineStartOffset + lineLengthChars - 1, 0)] == ' '; - Clay__WrappedTextLineArray_Add(&context->wrappedTextLines, CLAY__INIT(Clay__WrappedTextLine) { { lineWidth + (finalCharIsSpace ? -spaceWidth : 0), lineHeight }, { .length = lineLengthChars + (finalCharIsSpace ? -1 : 0), .chars = &textElementData->text.chars[lineStartOffset] } }); + Clay__WrappedTextLineArray_Add(&context->wrappedTextLines, CLAY__INIT(Clay__WrappedTextLine) { { lineWidth + (finalCharIsSpace ? -spaceWidth : 0) - (lineLengthChars > 0 ? containerElement->textConfig.letterSpacing : 0), lineHeight }, { .length = lineLengthChars + (finalCharIsSpace ? -1 : 0), .chars = &textElementData->text.chars[lineStartOffset] } }); textElementData->wrappedLines.length++; if (lineLengthChars == 0 || measuredWord->length == 0) { wordIndex = measuredWord->next; @@ -2623,7 +2623,7 @@ void Clay__CalculateFinalLayout(float deltaTime, bool useStoredBoundingBoxes, bo lineLengthChars = 0; lineStartOffset = measuredWord->startOffset; } else { - lineWidth += measuredWord->width + containerElement->textConfig.letterSpacing; + lineWidth += measuredWord->width; lineLengthChars += measuredWord->length; wordIndex = measuredWord->next; }