Adding hyphens with CFStringGetHyphenationLocationBeforeIndex
Asked Answered
E

1

4

I'm making a magazine with Core Text and I'm trying to automatically add hyphens to the text. I think I can do this with the function

CFStringGetHyphenationLocationBeforeIndex

But it's not working and I can't find any examples online.

What I'm trying to do is setting up the text and if I find any hyphen position I put a "-" there and replace the text via the accessor so it calls setNeedsDisplay and draws again from scratch.

- (void) setTexto:(NSAttributedString *)texto {

    _texto = texto;

    if (self.ctFrame != NULL) {
        CFRelease(self.ctFrame);
        self.ctFrame = NULL;
    }

    [self setNeedsDisplay];
}

-(void)drawRect:(CGRect)rect {

    CGContextRef context = UIGraphicsGetCurrentContext();   
    CGContextSetTextMatrix(context, CGAffineTransformIdentity);
    CGContextScaleCTM(context, 1.0, -1.0);

    CGMutablePathRef path = CGPathCreateMutable();
    CGPathAddRect(path, NULL, self.bounds);
    CTFramesetterRef ctFrameSetterRef = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)(_texto));
    self.ctFrame = CTFramesetterCreateFrame(ctFrameSetterRef, CFRangeMake(0, [_texto length]), path, NULL);

    NSArray *lines = (__bridge NSArray *)(CTFrameGetLines(self.ctFrame));

    CFIndex lineCount = [lines count];
    NSLog(@"lines: %d", lines.count);

    for(CFIndex idx = 0; idx < lineCount; idx++) {

        CTLineRef line = CFArrayGetValueAtIndex((CFArrayRef)lines, idx);

        CFRange lineStringRange = CTLineGetStringRange(line);
        NSRange lineRange = NSMakeRange(lineStringRange.location, lineStringRange.length);

        NSString* lineString = [self.texto.string substringWithRange:lineRange];

        CFStringRef localeIdent = CFSTR("es_ES");
        CFLocaleRef localeRef = CFLocaleCreate(kCFAllocatorDefault, localeIdent);

        NSUInteger breakAt = CFStringGetHyphenationLocationBeforeIndex((__bridge CFStringRef)lineString, lineRange.length, CFRangeMake(0, lineRange.length), 0, localeRef, NULL);

        if(breakAt!=-1) {
            NSRange replaceRange = NSMakeRange(lineRange.location+breakAt, 0);
            NSMutableAttributedString* attributedString = self.texto.mutableCopy;
            [attributedString replaceCharactersInRange:replaceRange withAttributedString:[[NSAttributedString alloc] initWithString:@"-"]];
            self.texto = attributedString.copy;
            break;
        } else {
            CGFloat ascent;
            CGFloat descent;
            CTLineGetTypographicBounds(line, &ascent, &descent, nil);
            CGContextSetTextPosition(context, 0.0, idx*-(ascent - 1 + descent)-ascent);
            CTLineDraw(line, context);
        }
    }
}

The problem is the second time it goes into drawRect (with the new text) lines.count log to 0. And also I'm not sure this is the correct way to do it.

Maybe there's another way to modify the CTLines (adding the remaining characters after the "-" from the previous line).

Evvy answered 15/4, 2013 at 15:4 Comment(0)
F
7

Before creating the attributed string you could add soft hyphenation. Then you could do the drawing.

I wrote a category for adding "soft hyphenation" to any string. These are "-" which is not visible when rendered, but instead merely queues for CoreText or UITextKit to know how to break up words.

NSString *string = @"accessibility tests and frameworks checking";
NSLocale *locale = [NSLocale localeWithLocaleIdentifier:@"en_US"];
NSString *hyphenatedString = [string softHyphenatedStringWithLocale:locale error:nil];
NSLog(@"%@", hyphenatedString);

Outputs ac-ces-si-bil-i-ty tests and frame-works check-ing


NSString+SoftHyphenation.h

typedef enum {
    NSStringSoftHyphenationErrorNotAvailableForLocale
} NSStringSoftHyphenationError;

extern NSString * const NSStringSoftHyphenationErrorDomain;
extern NSString * const NSStringSoftHyphenationToken;

@interface NSString (SoftHyphenation)

- (BOOL)canSoftHyphenateStringWithLocale:(NSLocale *)locale;
- (NSString *)softHyphenatedStringWithLocale:(NSLocale *)locale error:(out NSError **)error;

@end

NSString+SoftHyphenation.m

#import "NSString+SoftHyphenation.h"

NSString * const NSStringSoftHyphenationErrorDomain = @"NSStringSoftHyphenationErrorDomain";
NSString * const NSStringSoftHyphenationToken = @"­"; // NOTE: UTF-8 soft hyphen!

@implementation NSString (SoftHyphenation)

- (BOOL)canSoftHyphenateStringWithLocale:(NSLocale *)locale
{
    CFLocaleRef localeRef = (__bridge CFLocaleRef)(locale);
    return CFStringIsHyphenationAvailableForLocale(localeRef);
}

- (NSString *)softHyphenatedStringWithLocale:(NSLocale *)locale error:(out NSError **)error
{
    if(![self canSoftHyphenateStringWithLocale:locale])
    {
        if(error != NULL)
        {
            *error = [self hyphen_createOnlyError];
        }
        return [self copy];
    }
    else
    {
        NSMutableString *string = [self mutableCopy];
        unsigned char hyphenationLocations[string.length];
        memset(hyphenationLocations, 0, string.length);
        CFRange range = CFRangeMake(0, string.length);
        CFLocaleRef localeRef = (__bridge CFLocaleRef)(locale);

        for(int i = 0; i < string.length; i++)
        {
            CFIndex location = CFStringGetHyphenationLocationBeforeIndex((CFStringRef)string, i, range, 0, localeRef, NULL);

            if(location >= 0 && location < string.length)
            {
                hyphenationLocations[location] = 1;
            }
        }

        for(int i = string.length - 1; i > 0; i--)
        {
            if(hyphenationLocations[i])
            {

                [string insertString:NSStringSoftHyphenationToken atIndex:i];
            }
        }

        if(error != NULL) { *error = nil; }

        // Left here in case you want to test with visible results
        // return [string stringByReplacingOccurrencesOfString:NSStringSoftHyphenationToken withString:@"-"];
        return string;
    }
}

- (NSError *)hyphen_createOnlyError
{
    NSDictionary *userInfo = @{
                               NSLocalizedDescriptionKey: @"Hyphenation is not available for given locale",
                               NSLocalizedFailureReasonErrorKey: @"Hyphenation is not available for given locale",
                               NSLocalizedRecoverySuggestionErrorKey: @"You could try using a different locale even though it might not be 100% correct"
                               };
    return [NSError errorWithDomain:NSStringSoftHyphenationErrorDomain code:NSStringSoftHyphenationErrorNotAvailableForLocale userInfo:userInfo];
}

@end

Hope this helps :)

Fluff answered 8/11, 2013 at 11:58 Comment(6)
Actually it does help. But my question was asked long ago and I also developed my own solution. It also uses CFStringGetHyphenationLocationBeforeIndex but it does it when it's going to draw the text, only with lines longer than the text box. You can check it here: github.com/Odrakir/CoreTextHyphenationEvvy
@Evvy Nice! I like to separate drawing and setup though. :) I might as well publish my repo later :) Keep up the open source job :)Fluff
In this case, I guess you can understand the hyphenation as part of the drawing process, as it is only needed for lines longer than the text box when they're going to be drawn.Evvy
Nice, thank you! Is there a way to insert a "-" when the text breaks?Masqat
With CoreText? Well, you need to check if the last character of the line is a soft hyphen sign and then create a new line with a modified attributed string for that specific line. It breaks the line out from the typesetter and is terrible API wise.Fluff
There are many ways.. One way could be to just do this at draw time when drawing each line. Maybe this helps even though it is about truncation github.com/Cocoanetics/DTCoreText/blob/develop/Core/Source/…Fluff

© 2022 - 2024 — McMap. All rights reserved.