<commit_before>//******************************************************************************
//
// Copyright (c) Microsoft. All rights reserved.
//
// This code is licensed under the MIT License (MIT).
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
//******************************************************************************

#import <CoreText/CTLine.h>
#import <StubReturn.h>
#import "NSStringInternal.h"
#import "CoreTextInternal.h"
#import "CGContextInternal.h"
#import "DWriteWrapper_CoreText.h"
#import <CoreText/CTTypesetter.h>

#import <algorithm>
#import <numeric>
#import <vector>

static NSMutableAttributedString* _getTruncatedStringFromSourceLine(CTLineRef line,
                                                                    CTLineTruncationType truncationType,
                                                                    double widthToExtract);

@implementation _CTLine : NSObject
- (instancetype)init {
    if (self = [super init]) {
        _runs.attach(CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks));
    }
    return self;
}

- (instancetype)copyWithZone:(NSZone*)zone {
    _CTLine* ret = [_CTLine new];
    ret->_strRange = _strRange;
    ret->_width = _width;
    ret->_ascent = _ascent;
    ret->_descent = _descent;
    ret->_leading = _leading;
    ret->_glyphCount = _glyphCount;
    ret->_runs.attach(CFArrayCreateMutableCopy(kCFAllocatorDefault, 0, _runs));
    ret->_relativeXOffset = _relativeXOffset;

    return ret;
}
@end

/**
 @Status Interoperable
*/
CTLineRef CTLineCreateWithAttributedString(CFAttributedStringRef string) {
    return string ? static_cast<CTLineRef>(_DWriteGetLine(string)) : nil;
}

/**
 @Status Interoperable
*/
CTLineRef CTLineCreateTruncatedLine(CTLineRef sourceLine, double width, CTLineTruncationType truncationType, CTLineRef truncationToken) {
    if (sourceLine == nil) {
        return nil;
    }

    // get the truncationToken width, use it to calculate the exact width that should be extracted from the sourceLine.
    CGFloat truncationTokenWidth;
    if (truncationToken == nil) {
        truncationTokenWidth = 0.0f;
    } else {
        truncationTokenWidth = (static_cast<_CTLine*>(truncationToken))->_width;
    }

    if (width <= 0 || truncationTokenWidth > width) {
        return nil;
    }

    CGFloat sourceLineWidth = static_cast<_CTLine*>(sourceLine)->_width;
    if (width >= sourceLineWidth || sourceLineWidth == truncationTokenWidth) {
        // return a copy of sourceLine
        return static_cast<CTLineRef>([static_cast<_CTLine*>(sourceLine) copy]);
    }

    // widthToExtract is the width that will be extracted from the sourceLine and merged with truncationToken finally
    double widthToExtract;
    if (truncationToken == nil) {
        widthToExtract = width;
    } else {
        widthToExtract = width - truncationTokenWidth;
    }

    // get an NSAttributed string from truncationToken by looping across its runs and extracting the run attribuets.
    NSMutableAttributedString* stringFromToken = [NSMutableAttributedString new];
    CFArrayRef tokenRuns = CTLineGetGlyphRuns(truncationToken);
    if (tokenRuns != nil) {
        CFIndex numberOfRuns = CFArrayGetCount(tokenRuns);
        for (int i = 0; i < numberOfRuns; ++i) {
            CTRunRef run = static_cast<CTRunRef>(CFArrayGetValueAtIndex(tokenRuns, i));
            CFDictionaryRef attribs = CTRunGetAttributes(run);
            NSAttributedString* string = [[NSAttributedString alloc] initWithString:static_cast<NSString*>(run->_stringFragment.get())
                                                                         attributes:(NSDictionary*)attribs];
            [stringFromToken appendAttributedString:string];
            [string release];
        }
    }

    NSMutableAttributedString* finalString = nil;
    NSMutableAttributedString* truncatedStringFromLeft = nil;
    NSMutableAttributedString* truncatedStringFromRight = nil;

    switch (truncationType) {
        case kCTLineTruncationStart:
            truncatedStringFromRight = _getTruncatedStringFromSourceLine(sourceLine, kCTLineTruncationStart, widthToExtract);
            [stringFromToken appendAttributedString:truncatedStringFromRight];
            finalString = stringFromToken;

            break;
        case kCTLineTruncationMiddle:
            widthToExtract = widthToExtract / 2;
            truncatedStringFromLeft = _getTruncatedStringFromSourceLine(sourceLine, kCTLineTruncationEnd, widthToExtract);
            truncatedStringFromRight = _getTruncatedStringFromSourceLine(sourceLine, kCTLineTruncationStart, widthToExtract);
            [truncatedStringFromLeft appendAttributedString:stringFromToken];
            [truncatedStringFromLeft appendAttributedString:truncatedStringFromRight];
            finalString = truncatedStringFromLeft;

            break;
        case kCTLineTruncationEnd:
            truncatedStringFromLeft = _getTruncatedStringFromSourceLine(sourceLine, kCTLineTruncationEnd, widthToExtract);
            [truncatedStringFromLeft appendAttributedString:stringFromToken];
            finalString = truncatedStringFromLeft;

            break;
        default:
            return nil;
    }

    CTLineRef ret = CTLineCreateWithAttributedString(static_cast<CFAttributedStringRef>(finalString));
    [stringFromToken release];

    return ret;
}

static NSMutableAttributedString* _getTruncatedStringFromSourceLine(CTLineRef sourceLine,
                                                                    CTLineTruncationType truncationType,
                                                                    double widthToExtract) {
    NSMutableAttributedString* ret = [NSMutableAttributedString new];
    CFArrayRef glyphRuns = CTLineGetGlyphRuns(sourceLine);
    CFIndex numberOfRuns = CFArrayGetCount(glyphRuns);
    double extractedWidth = 0;
    bool done = 0;

    for (int i = 0; i < numberOfRuns; ++i) {
        int runIndex;
        if (truncationType == kCTLineTruncationStart) {
            runIndex = numberOfRuns - i - 1;
        } else if (truncationType == kCTLineTruncationEnd) {
            runIndex = i;
        }

        CTRunRef run = static_cast<CTRunRef>(CFArrayGetValueAtIndex(glyphRuns, runIndex));
        NSMutableString* runString = [NSMutableString new];

        int numberOfGlyphs = run->_dwriteGlyphRun.glyphCount;
        for (int j = 0; j < numberOfGlyphs; ++j) {
            int glyphIndex;
            if (truncationType == kCTLineTruncationStart) {
                glyphIndex = numberOfGlyphs - j - 1;
            } else if (truncationType == kCTLineTruncationEnd) {
                glyphIndex = j;
            }

            if (extractedWidth + run->_dwriteGlyphRun.glyphAdvances[glyphIndex] > widthToExtract) {
                done = 1;
                break;
            }

            extractedWidth += run->_dwriteGlyphRun.glyphAdvances[glyphIndex];
            char glyph = CFStringGetCharacterAtIndex(run->_stringFragment, glyphIndex);
            [runString appendString:[NSString stringWithFormat:@"%c", glyph]];
        }

        CFDictionaryRef attribs = CTRunGetAttributes(run);

        if (truncationType == kCTLineTruncationStart) {
            NSString* reverse = [runString _reverseString];
            NSAttributedString* string = [[NSAttributedString alloc] initWithString:reverse attributes:(NSDictionary*)attribs];
            [ret insertAttributedString:string atIndex:0];
            [string release];
        } else if (truncationType == kCTLineTruncationEnd) {
            NSAttributedString* string = [[NSAttributedString alloc] initWithString:runString attributes:(NSDictionary*)attribs];
            [ret appendAttributedString:string];<commit_msg>Implementation of reverse string. - Implemented via _wcsrev - Reduced string copies - Implemented using CoreFoundation - Removed usage of Foundation associated with reverse string.<commit_after>//******************************************************************************
//
// Copyright (c) Microsoft. All rights reserved.
//
// This code is licensed under the MIT License (MIT).
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
//******************************************************************************

#import <CoreText/CTLine.h>
#import <StubReturn.h>
#import "CoreTextInternal.h"
#import "CGContextInternal.h"
#import "DWriteWrapper_CoreText.h"
#import <CoreText/CTTypesetter.h>
#import <CoreFoundation/CFString.h>

#include <memory>
#import <algorithm>
#import <numeric>
#import <cwchar>
#import <vector>

static CFStringRef __CTCreateReversedString(CFStringRef string) {
    if (string == nullptr) {
        return nullptr;
    }

    CFIndex length = CFStringGetLength(string);
    CFIndex usedBufLen;
    CFStringGetBytes(string, CFRangeMake(0, length), kCFStringEncodingUTF16, 0, false, nullptr, length, &usedBufLen);

    if (length < 2) {
        return CFStringCreateCopy(kCFAllocatorDefault, string);
    }

    CFIndex bufLen = (usedBufLen / sizeof(UniChar));
    std::unique_ptr<UniChar[]> characters(new UniChar[bufLen + 1]);
    characters[bufLen] = L'\0';

    CFStringGetBytes(
        string, CFRangeMake(0, length), kCFStringEncodingUTF16, 0, false, reinterpret_cast<UInt8*>(characters.get()), usedBufLen, nullptr);

    wchar_t* result = _wcsrev(reinterpret_cast<wchar_t*>(characters.get()));
    if (result == nullptr) {
        return nullptr;
    }

    return CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, characters.release(), bufLen, nullptr);
}

static NSMutableAttributedString* _getTruncatedStringFromSourceLine(CTLineRef line,
                                                                    CTLineTruncationType truncationType,
                                                                    double widthToExtract);

@implementation _CTLine : NSObject
- (instancetype)init {
    if (self = [super init]) {
        _runs.attach(CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks));
    }
    return self;
}

- (instancetype)copyWithZone:(NSZone*)zone {
    _CTLine* ret = [_CTLine new];
    ret->_strRange = _strRange;
    ret->_width = _width;
    ret->_ascent = _ascent;
    ret->_descent = _descent;
    ret->_leading = _leading;
    ret->_glyphCount = _glyphCount;
    ret->_runs.attach(CFArrayCreateMutableCopy(kCFAllocatorDefault, 0, _runs));
    ret->_relativeXOffset = _relativeXOffset;

    return ret;
}
@end

/**
 @Status Interoperable
*/
CTLineRef CTLineCreateWithAttributedString(CFAttributedStringRef string) {
    return string ? static_cast<CTLineRef>(_DWriteGetLine(string)) : nil;
}

/**
 @Status Interoperable
*/
CTLineRef CTLineCreateTruncatedLine(CTLineRef sourceLine, double width, CTLineTruncationType truncationType, CTLineRef truncationToken) {
    if (sourceLine == nil) {
        return nil;
    }

    // get the truncationToken width, use it to calculate the exact width that should be extracted from the sourceLine.
    CGFloat truncationTokenWidth;
    if (truncationToken == nil) {
        truncationTokenWidth = 0.0f;
    } else {
        truncationTokenWidth = (static_cast<_CTLine*>(truncationToken))->_width;
    }

    if (width <= 0 || truncationTokenWidth > width) {
        return nil;
    }

    CGFloat sourceLineWidth = static_cast<_CTLine*>(sourceLine)->_width;
    if (width >= sourceLineWidth || sourceLineWidth == truncationTokenWidth) {
        // return a copy of sourceLine
        return static_cast<CTLineRef>([static_cast<_CTLine*>(sourceLine) copy]);
    }

    // widthToExtract is the width that will be extracted from the sourceLine and merged with truncationToken finally
    double widthToExtract;
    if (truncationToken == nil) {
        widthToExtract = width;
    } else {
        widthToExtract = width - truncationTokenWidth;
    }

    // get an NSAttributed string from truncationToken by looping across its runs and extracting the run attribuets.
    NSMutableAttributedString* stringFromToken = [NSMutableAttributedString new];
    CFArrayRef tokenRuns = CTLineGetGlyphRuns(truncationToken);
    if (tokenRuns != nil) {
        CFIndex numberOfRuns = CFArrayGetCount(tokenRuns);
        for (int i = 0; i < numberOfRuns; ++i) {
            CTRunRef run = static_cast<CTRunRef>(CFArrayGetValueAtIndex(tokenRuns, i));
            CFDictionaryRef attribs = CTRunGetAttributes(run);
            NSAttributedString* string = [[NSAttributedString alloc] initWithString:static_cast<NSString*>(run->_stringFragment.get())
                                                                         attributes:(NSDictionary*)attribs];
            [stringFromToken appendAttributedString:string];
            [string release];
        }
    }

    NSMutableAttributedString* finalString = nil;
    NSMutableAttributedString* truncatedStringFromLeft = nil;
    NSMutableAttributedString* truncatedStringFromRight = nil;

    switch (truncationType) {
        case kCTLineTruncationStart:
            truncatedStringFromRight = _getTruncatedStringFromSourceLine(sourceLine, kCTLineTruncationStart, widthToExtract);
            [stringFromToken appendAttributedString:truncatedStringFromRight];
            finalString = stringFromToken;

            break;
        case kCTLineTruncationMiddle:
            widthToExtract = widthToExtract / 2;
            truncatedStringFromLeft = _getTruncatedStringFromSourceLine(sourceLine, kCTLineTruncationEnd, widthToExtract);
            truncatedStringFromRight = _getTruncatedStringFromSourceLine(sourceLine, kCTLineTruncationStart, widthToExtract);
            [truncatedStringFromLeft appendAttributedString:stringFromToken];
            [truncatedStringFromLeft appendAttributedString:truncatedStringFromRight];
            finalString = truncatedStringFromLeft;

            break;
        case kCTLineTruncationEnd:
            truncatedStringFromLeft = _getTruncatedStringFromSourceLine(sourceLine, kCTLineTruncationEnd, widthToExtract);
            [truncatedStringFromLeft appendAttributedString:stringFromToken];
            finalString = truncatedStringFromLeft;

            break;
        default:
            return nil;
    }

    CTLineRef ret = CTLineCreateWithAttributedString(static_cast<CFAttributedStringRef>(finalString));
    [stringFromToken release];

    return ret;
}

static NSMutableAttributedString* _getTruncatedStringFromSourceLine(CTLineRef sourceLine,
                                                                    CTLineTruncationType truncationType,
                                                                    double widthToExtract) {
    NSMutableAttributedString* ret = [NSMutableAttributedString new];
    CFArrayRef glyphRuns = CTLineGetGlyphRuns(sourceLine);
    CFIndex numberOfRuns = CFArrayGetCount(glyphRuns);
    double extractedWidth = 0;
    bool done = 0;

    for (int i = 0; i < numberOfRuns; ++i) {
        int runIndex;
        if (truncationType == kCTLineTruncationStart) {
            runIndex = numberOfRuns - i - 1;
        } else if (truncationType == kCTLineTruncationEnd) {
            runIndex = i;
        }

        CTRunRef run = static_cast<CTRunRef>(CFArrayGetValueAtIndex(glyphRuns, runIndex));
        NSMutableString* runString = [NSMutableString new];

        int numberOfGlyphs = run->_dwriteGlyphRun.glyphCount;
        for (int j = 0; j < numberOfGlyphs; ++j) {
            int glyphIndex;
            if (truncationType == kCTLineTruncationStart) {
                glyphIndex = numberOfGlyphs - j - 1;
            } else if (truncationType == kCTLineTruncationEnd) {
                glyphIndex = j;
            }

            if (extractedWidth + run->_dwriteGlyphRun.glyphAdvances[glyphIndex] > widthToExtract) {
                done = 1;
                break;
            }

            extractedWidth += run->_dwriteGlyphRun.glyphAdvances[glyphIndex];
            char glyph = CFStringGetCharacterAtIndex(run->_stringFragment, glyphIndex);
            [runString appendString:[NSString stringWithFormat:@"%c", glyph]];
        }

        CFDictionaryRef attribs = CTRunGetAttributes(run);

        if (truncationType == kCTLineTruncationStart) {
            auto reverse = woc::MakeStrongCF<CFStringRef>(__CTCreateReversedString(static_cast<CFStringRef>(runString)));
            NSAttributedString* string =
                [[NSAttributedString alloc] initWithString:static_cast<NSString*>(reverse.get()) attributes:(NSDictionary*)attribs];
            [ret insertAttributedString:string atIndex:0];
            [string release];
        } else if (truncationType == kCTLineTruncationEnd) {
            NSAttributedString* string = [[NSAttributedString alloc] initWithString:runString attributes:(NSDictionary*)attribs];
            [ret appendAttributedString:string];
{'fraction_non_alphanumeric': 0.09983577779036186, 'fraction_numerical': 0.0023783906223455464, 'mean_word_length': 3.042114900434882, 'pattern_counts': {'":': 0, '<': 51, '<?xml version=': 0, '>': 78, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 22, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6788283', 'n_tokens_mistral': 4968, 'n_tokens_neox': 4666, 'n_words': 1096}