From 3dffebab120ae225050df49300cd75661841fc85 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Mon, 21 May 2018 13:02:34 -0700 Subject: [PATCH] Update the text layout compatibility logic --- Source/ASTextNode2.mm | 43 +++++++++++-------- .../TextExperiment/Component/ASTextLayout.m | 4 +- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/Source/ASTextNode2.mm b/Source/ASTextNode2.mm index 376f8b316..c663def90 100644 --- a/Source/ASTextNode2.mm +++ b/Source/ASTextNode2.mm @@ -401,28 +401,34 @@ + (ASTextLayout *)compatibleLayoutWithContainer:(ASTextContainer *)container layoutCacheLock.unlock(); CGRect containerBounds = (CGRect){ .size = container.size }; + NSRange textRange = NSMakeRange(0, text.string.length); { for (auto &t : cacheValue->_layouts) { CGSize constrainedSize = std::get<0>(t); ASTextLayout *layout = std::get<1>(t); - CGSize layoutSize = layout.textBoundingSize; - // 1. CoreText can return frames that are narrower than the constrained width, for obvious reasons. - // 2. CoreText can return frames that are slightly wider than the constrained width, for some reason. - // We have to trust that somehow it's OK to try and draw within our size constraint, despite the return value. - // 3. Thus, those two values (constrained width & returned width) form a range, where - // intermediate values in that range will be snapped. Thus, we can use a given layout as long as our - // width is in that range, between the min and max of those two values. - CGRect minRect = CGRectMake(0, 0, MIN(layoutSize.width, constrainedSize.width), MIN(layoutSize.height, constrainedSize.height)); - if (!CGRectContainsRect(containerBounds, minRect)) { - continue; - } - CGRect maxRect = CGRectMake(0, 0, MAX(layoutSize.width, constrainedSize.width), MAX(layoutSize.height, constrainedSize.height)); - if (!CGRectContainsRect(maxRect, containerBounds)) { - continue; - } - if (!CGSizeEqualToSize(container.size, constrainedSize)) { - continue; + BOOL wrapped = (layout.rowCount >= 2); + BOOL truncated = !NSEqualRanges(layout.visibleRange, textRange); + BOOL wrappingDimensionsMatch = (layout.container.verticalForm ? constrainedSize.height == containerBounds.size.height : constrainedSize.width == containerBounds.size.width); + BOOL nonWrappingDimensionIsGreater = (layout.container.verticalForm ? containerBounds.size.width >= constrainedSize.width : containerBounds.size.height >= constrainedSize.height); + + if (!wrapped && !truncated) { + // Not wrapped, not truncated, can reuse if our bounds are bigger. + if (!CGRectContainsRect(containerBounds, layout.textBoundingRect)) { + continue; + } + } else if (!truncated) { + // Wrapped, but not truncated. Can reuse layout if wrapping dimensions match + // and if the new constrained non-wrapping dimension is greater. + if (!wrappingDimensionsMatch || !nonWrappingDimensionIsGreater) { + continue; + } + } else { + // Truncated. For now require that the sizes match. There may be other + // false negative cases in the future. + if (!CGSizeEqualToSize(containerBounds.size, constrainedSize)) { + continue; + } } // Now check container params. @@ -442,6 +448,9 @@ + (ASTextLayout *)compatibleLayoutWithContainer:(ASTextContainer *)container if (!ASObjectIsEqual(container.truncationToken, otherContainer.truncationToken)) { continue; } + if (container.verticalForm != otherContainer.verticalForm) { + continue; + } // TODO: When we get a cache hit, move this entry to the front (LRU). return layout; } diff --git a/Source/Private/TextExperiment/Component/ASTextLayout.m b/Source/Private/TextExperiment/Component/ASTextLayout.m index 3707e3d94..a9321405b 100755 --- a/Source/Private/TextExperiment/Component/ASTextLayout.m +++ b/Source/Private/TextExperiment/Component/ASTextLayout.m @@ -665,8 +665,8 @@ + (ASTextLayout *)layoutWithContainer:(ASTextContainer *)container text:(NSAttri size.height += rect.origin.y; if (size.width < 0) size.width = 0; if (size.height < 0) size.height = 0; - size.width = ceil(size.width); - size.height = ceil(size.height); + size.width = ASCeilPixelValue(size.width); + size.height = ASCeilPixelValue(size.height); textBoundingSize = size; }