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.
This commit is contained in:
K. Adam Christensen 2026-07-12 16:50:54 -07:00
parent e6cc36941a
commit 93e28a2454
No known key found for this signature in database

10
clay.h
View file

@ -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;
}