Drop cap with NSAttributedString
Chciałbym zrobić drop cap pierwszy znak w UILabel
używając attributedText
NSAttributedString
tylko własność. Tak:
Http://www.interpretationbydesign.com/wp-content/uploads/2009/02/drop_caps.gif
Eksperymentowałem z dostosowaniem linii podstawowej dla zakresu pierwszego znaku do wartości ujemnej i działa to na wyrównanie górnej części pierwszego znaku z górną częścią reszty pierwszej linii. Ale nie znalazłem sposobu, aby inne linie płynęły w prawo o charakterze kropli.
Czy można to rozwiązać za pomocą NSAttributedString only
, czy też muszę podzielić łańcuch i renderować go samemu używając Core Text?
4 answers
CoreText nie może wykonywać Drop caps, ponieważ składa się z linii złożonych z przebiegów glifów. Drop cap obejmowałby wiele linii, które nie są obsługiwane.
Aby osiągnąć ten efekt, należy narysować nasadkę oddzielnie, a następnie narysować resztę tekstu w ścieżce, która go okrąża.
W skrócie: nie jest to możliwe w UILabel, możliwe, ale sporo pracy z CoreText.
Kroki, aby to zrobić z CoreText to:
- Utwórz framesetter dla pojedynczego charakter.
- get its bounds
- Utwórz ścieżkę, która oszczędza ramkę Drop cap
- Utwórz framesetter dla pozostałych znaków z tą ścieżką
- narysuj pierwszy glif
- narysuj resztę
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-02-01 06:27:31
Jak wszyscy wspominali, nie można tego zrobić tylko NSAttributedString
. Nikolai ma właściwe podejście, używając CTFrameSetters
. Jednak jest możliwe, aby Ustawiacz ramek renderował tekst w określonym obszarze (tj. zdefiniowanym przez ścieżkę CGPath).
Będziesz musiał utworzyć 2 framesetters, jeden dla drop cap, a drugi dla reszty tekstu.
Następnie chwytasz ramkę zrzutu i budujesz CGPathRef
, która biegnie wokół przestrzeni ramki zrzutu cap.
Następnie renderujesz oba zestawy ramek do widoku.
Stworzyłem przykładowy projekt z obiektem o nazwie DropCapView, który jest podklasą UIView. Ten widok renderuje pierwszy znak i owija pozostały tekst wokół niego.
Wygląda tak:
Jest kilka kroków, więc dodałem link do projektu github hostującego przykład. W projekcie są komentarze, które Ci pomogą.
Będziesz musiał bawić się kształtem elementu textBox
(tj. CGPathRef), aby wyściełać brzegi widoku i dokręcić go do litery Drop cap.
Oto bebechy metody rysowania:
- (void)drawRect:(CGRect)rect {
//make sure that all the variables exist and are non-nil
NSAssert(_text != nil, @"text is nil");
NSAssert(_textColor != nil, @"textColor is nil");
NSAssert(_fontName != nil, @"fontName is nil");
NSAssert(_dropCapFontSize > 0, @"dropCapFontSize is <= 0");
NSAssert(_textFontSize > 0, @"textFontSize is <=0");
//convert the text aligment from NSTextAligment to CTTextAlignment
CTTextAlignment ctTextAlignment = NSTextAlignmentToCTTextAlignment(_textAlignment);
//create a paragraph style
CTParagraphStyleSetting paragraphStyleSettings[] = { {
.spec = kCTParagraphStyleSpecifierAlignment,
.valueSize = sizeof ctTextAlignment,
.value = &ctTextAlignment
}
};
CFIndex settingCount = sizeof paragraphStyleSettings / sizeof *paragraphStyleSettings;
CTParagraphStyleRef style = CTParagraphStyleCreate(paragraphStyleSettings, settingCount);
//create two fonts, with the same name but differing font sizes
CTFontRef dropCapFontRef = CTFontCreateWithName((__bridge CFStringRef)_fontName, _dropCapFontSize, NULL);
CTFontRef textFontRef = CTFontCreateWithName((__bridge CFStringRef)_fontName, _textFontSize, NULL);
//create a dictionary of style elements for the drop cap letter
NSDictionary *dropCapDict = [NSDictionary dictionaryWithObjectsAndKeys:
(__bridge id)dropCapFontRef, kCTFontAttributeName,
_textColor.CGColor, kCTForegroundColorAttributeName,
style, kCTParagraphStyleAttributeName,
@(_dropCapKernValue) , kCTKernAttributeName,
nil];
//convert it to a CFDictionaryRef
CFDictionaryRef dropCapAttributes = (__bridge CFDictionaryRef)dropCapDict;
//create a dictionary of style elements for the main text body
NSDictionary *textDict = [NSDictionary dictionaryWithObjectsAndKeys:
(__bridge id)textFontRef, kCTFontAttributeName,
_textColor.CGColor, kCTForegroundColorAttributeName,
style, kCTParagraphStyleAttributeName,
nil];
//convert it to a CFDictionaryRef
CFDictionaryRef textAttributes = (__bridge CFDictionaryRef)textDict;
//clean up, because the dictionaries now have copies
CFRelease(dropCapFontRef);
CFRelease(textFontRef);
CFRelease(style);
//create an attributed string for the dropcap
CFAttributedStringRef dropCapString = CFAttributedStringCreate(kCFAllocatorDefault,
(__bridge CFStringRef)[_text substringToIndex:1],
dropCapAttributes);
//create an attributed string for the text body
CFAttributedStringRef textString = CFAttributedStringCreate(kCFAllocatorDefault,
(__bridge CFStringRef)[_text substringFromIndex:1],
textAttributes);
//create an frame setter for the dropcap
CTFramesetterRef dropCapSetter = CTFramesetterCreateWithAttributedString(dropCapString);
//create an frame setter for the dropcap
CTFramesetterRef textSetter = CTFramesetterCreateWithAttributedString(textString);
//clean up
CFRelease(dropCapString);
CFRelease(textString);
//get the size of the drop cap letter
CFRange range;
CGSize maxSizeConstraint = CGSizeMake(200.0f, 200.0f);
CGSize dropCapSize = CTFramesetterSuggestFrameSizeWithConstraints(dropCapSetter,
CFRangeMake(0, 1),
dropCapAttributes,
maxSizeConstraint,
&range);
//create the path that the main body of text will be drawn into
//i create the path based on the dropCapSize
//adjusting to tighten things up (e.g. the *0.8,done by eye)
//to get some padding around the edges of the screen
//you could go to +5 (x) and self.frame.size.width -5 (same for height)
CGMutablePathRef textBox = CGPathCreateMutable();
CGPathMoveToPoint(textBox, nil, dropCapSize.width, 0);
CGPathAddLineToPoint(textBox, nil, dropCapSize.width, dropCapSize.height * 0.8);
CGPathAddLineToPoint(textBox, nil, 0, dropCapSize.height * 0.8);
CGPathAddLineToPoint(textBox, nil, 0, self.frame.size.height);
CGPathAddLineToPoint(textBox, nil, self.frame.size.width, self.frame.size.height);
CGPathAddLineToPoint(textBox, nil, self.frame.size.width, 0);
CGPathCloseSubpath(textBox);
//create a transform which will flip the CGContext into the same orientation as the UIView
CGAffineTransform flipTransform = CGAffineTransformIdentity;
flipTransform = CGAffineTransformTranslate(flipTransform,
0,
self.bounds.size.height);
flipTransform = CGAffineTransformScale(flipTransform, 1, -1);
//invert the path for the text box
CGPathRef invertedTextBox = CGPathCreateCopyByTransformingPath(textBox,
&flipTransform);
CFRelease(textBox);
//create the CTFrame that will hold the main body of text
CTFrameRef textFrame = CTFramesetterCreateFrame(textSetter,
CFRangeMake(0, 0),
invertedTextBox,
NULL);
CFRelease(invertedTextBox);
CFRelease(textSetter);
//create the drop cap text box
//it is inverted already because we don't have to create an independent cgpathref (like above)
CGPathRef dropCapTextBox = CGPathCreateWithRect(CGRectMake(_dropCapKernValue/2.0f,
0,
dropCapSize.width,
dropCapSize.height),
&flipTransform);
CTFrameRef dropCapFrame = CTFramesetterCreateFrame(dropCapSetter,
CFRangeMake(0, 0),
dropCapTextBox,
NULL);
CFRelease(dropCapTextBox);
CFRelease(dropCapSetter);
//draw the frames into our graphic context
CGContextRef gc = UIGraphicsGetCurrentContext();
CGContextSaveGState(gc); {
CGContextConcatCTM(gc, flipTransform);
CTFrameDraw(dropCapFrame, gc);
CTFrameDraw(textFrame, gc);
} CGContextRestoreGState(gc);
CFRelease(dropCapFrame);
CFRelease(textFrame);
}
P. S. to pochodzi z inspiracji: https://stackoverflow.com/a/9272955/1218605
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:54:27
Nie, Nie można tego zrobić tylko za pomocą NSAttributedString
i standardowego ciągu znaków.
Ponieważ Drop cap jest właściwością akapitu, CTParagraphStyle
musi zawierać informacje o drop cap. Jedyną właściwością CTParagraphStyle
, która wpływa na wcięcie początku akapitu jest kCTParagraphStyleSpecifierFirstLineHeadIndent
, ale dotyczy tylko pierwszego wiersza.
Po prostu nie ma sposobu, aby powiedzieć CTFramesetter
Jak obliczyć początek dla drugiego i więcej wierszy.
Jedynym sposobem jest zdefiniowanie własnego atrybut i zapis kodu, aby narysować ciąg znaków za pomocą CTFramesetter
i CTTypesetter
, które potwierdzają ten niestandardowy atrybut.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-25 11:31:58
Nie jest to idealne rozwiązanie, ale powinieneś spróbować DTCoreText i renderować swoje normalne NSString
jako formatted HTML
. W HTML można "upuścić" literę.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-25 10:48:36