Skip to content

Instantly share code, notes, and snippets.

@pnanh-github
Created October 15, 2018 08:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pnanh-github/715c350def4a196c89949b765ee9e99f to your computer and use it in GitHub Desktop.
Save pnanh-github/715c350def4a196c89949b765ee9e99f to your computer and use it in GitHub Desktop.
CTRVerticalTextView.m
//
// Copyright (c) 2012 Takuma Shimizu
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// 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.
//
/*NSLogの代わり*/
#ifdef DEBUG
#define DEBUGLOG(...) //NSLog(__VA_ARGS__)
#else
#define DEBUGLOG(...) ;
#endif
/*改行*/
#ifdef DEBUG
#define BREAK() //NSLog(@"\n")
#else
#define BREAK()
#endif
#import "CTRVerticalTextView.h"
#import "Utilities.h"
@interface CTRVerticalTextView ()
@end
@implementation CTRVerticalTextView
- (id)init
{
self = [super init];
if (self) [self initLayout];
return self;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) [self initLayout];
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) [self initLayout];
return self;
}
- (void)initLayout
{
_text = @"";
_titleText = @"";
_titleSizeRate = 1.5f;
_titleForTextSpace = 0;
_fontSize = 18.0f;
_lineSpace = 12.0f;
_letterSpace = 3.0f;
_fontName = @"HiraKakuProN-W3";
}
//////////////////////////////////////////////////////////////
#pragma mark - Setter
//////////////////////////////////////////////////////////////
- (void)setFontSize:(CGFloat)fontSize
{
if (fontSize < 1.0f) {
fontSize = 16.0f;
}
_fontSize = fontSize;
[self setNeedsDisplay];
}
- (void)setLineSpace:(CGFloat)lineSpace
{
if (lineSpace < 1.0f) {
lineSpace = 5.0f;
}
_lineSpace = lineSpace;
[self setNeedsDisplay];
}
- (void)setLetterSpace:(CGFloat)letterSpace
{
// if (letterSpace < 1.0f) {
// letterSpace = 3.0f;
// }
_letterSpace = letterSpace;
[self setNeedsDisplay];
}
- (void)setFontName:(NSString *)fontName
{
if ([_fontName length] < 1) {
fontName = @"HiraKakuProN-W3";
}
_fontName = fontName;
[self setNeedsDisplay];
}
//////////////////////////////////////////////////////////////
#pragma mark - CoreText
//////////////////////////////////////////////////////////////
- (void)drawRect:(CGRect)rect
{
// FUNC();
// DEBUGLOG(@"%@",NSStringFromCGRect(rect));
// if ([self.text length] < 1) return;
// CFAttributedString -> CTFramesetter + CGPath -> CTFrame
// 上記の順でCoreTextを作成
NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] init];
// 部・章などのタイトルがあれば書く
if ([self.titleText length] > 0) {
_titleForTextSpace = 30.0f;
CGFloat fontSize = [self.text length] < 1 ? 27.0f : self.fontSize * _titleSizeRate; // 書籍タイトルのみの表示時はサイズを固定する
CTFontRef titleFont = CTFontCreateWithName((__bridge CFStringRef)self.fontName, fontSize, NULL);
NSMutableDictionary *titleAttrDict = [self getAttributedStringSourceWithString:(CFStringRef)self.titleText andFont:titleFont andiSJP_ZH:YES];
NSMutableAttributedString *titleAttrString = [[NSMutableAttributedString alloc] initWithString:self.titleText attributes:titleAttrDict];
[attrString appendAttributedString:titleAttrString];
_titleForTextSpace = 0;
}
// 本文の内容部分の属性文字列を加える
if ([self.text length] > 0) {
CTFontRef font = CTFontCreateWithName((__bridge CFStringRef)self.fontName, self.fontSize, NULL);
BOOL isJp = [Utilities stringIsJapanese:@""] || [Utilities stringIsChinese:@""];
NSMutableDictionary *textAttrDict = [self getAttributedStringSourceWithString:(CFStringRef)self.text andFont:font andiSJP_ZH:isJp];
NSMutableAttributedString *textAttrString = [[NSMutableAttributedString alloc] initWithString:self.text attributes:textAttrDict];
[attrString appendAttributedString:textAttrString];
CFRelease(font);
}
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)attrString);
CGMutablePathRef path = CGPathCreateMutable();
CGSize pathSize = rect.size;
// 縦書きにするため幅と高さが逆転する。逆転するだけだと枠外にいってしまうので、幅と高さの差で引き戻す。(iOS 5)
// また、原点が左下に変わっている(縦書き準備 3/3のScaleが影響)。このことを頭に入れて↓
// iOS5では幅と高さが逆転するため、それらを入れ替えた際に生じる差を埋める必要があるが、iOS6では逆転が起こらないので不必要
CGFloat reversingDiff = DEVICE_OS_MAJOR_VERSION <= 5 ? pathSize.height - pathSize.width : 0;
// 幅を高さの値に=>右側へ行きすぎる=>減算 (iOS 5)
// 高さを幅の値に=>下方へ行きすぎる=>加算 (iOS 5)
CGPathAddRect(path, NULL, CGRectMake(-reversingDiff, reversingDiff, BELOW_IOS_5_REVERSE_SIZE_WIDTH(pathSize), BELOW_IOS_5_REVERSE_SIZE_HEIGHT(pathSize)));
// DEBUGLOG(@"path: %@",NSStringFromCGRect(CGPathGetBoundingBox(path)));
CFRange fitRange = CFRangeMake(0, 0);
/*CGSize debug_size = */
CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0, 0), NULL, CGSizeMake(BELOW_IOS_5_REVERSE_SIZE_WIDTH(pathSize), BELOW_IOS_5_REVERSE_SIZE_HEIGHT(pathSize)), &fitRange);
// DEBUGLOG(@"frame: %@",NSStringFromCGSize(debug_size));
NSDictionary *frameDict = @{ (id)kCTFrameProgressionAttributeName : @(kCTFrameProgressionRightToLeft) }; // 縦書き準備 2/3
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, fitRange, path, (__bridge CFDictionaryRef)frameDict);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
// 縦書き準備 3/3 鏡状態になっているので反転
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CGContextTranslateCTM(context, 0, pathSize.height);
CGContextScaleCTM(context, 1.0f, -1.0f);
CTFrameDraw(frame, context); // contextに対してCoreTextテキストを書き込む
CGContextRestoreGState(context);
// 掃除
CGPathRelease(path);
CFRelease(framesetter);
CFRelease(frame);
}
//////////////////////////////////////////////////////////////
#pragma mark - Private
//////////////////////////////////////////////////////////////
- (NSMutableDictionary *)getAttributedStringSourceWithString:(CFStringRef)stringRef andFont:(CTFontRef)fontRef andiSJP_ZH:(BOOL) value
{
// グリフを日本式に最適化
CTGlyphInfoRef glyphInfo = CTGlyphInfoCreateWithCharacterIdentifier(kCGFontIndexMax, kCTAdobeJapan2CharacterCollection, stringRef);
// アライメントと自動改行と行間の設定
CTTextAlignment alignment = kCTLeftTextAlignment;
CTLineBreakMode lineBreakMode = kCTLineBreakByCharWrapping;
CGFloat lineSpace = self.lineSpace;
CGFloat paragraphSpace = _titleForTextSpace;
CTParagraphStyleSetting paragraphStypeSettings[] = {
{kCTParagraphStyleSpecifierAlignment, sizeof(CTTextAlignment), &alignment},
{kCTParagraphStyleSpecifierLineBreakMode, sizeof(CTLineBreakMode), &lineBreakMode},
{kCTParagraphStyleSpecifierParagraphSpacing, sizeof(CGFloat), &paragraphSpace},
{kCTParagraphStyleSpecifierMinimumLineSpacing, sizeof(CGFloat), &lineSpace},
{kCTParagraphStyleSpecifierMaximumLineSpacing, sizeof(CGFloat), &lineSpace},
};
CTParagraphStyleRef paragraphStyle = CTParagraphStyleCreate(paragraphStypeSettings, sizeof(paragraphStypeSettings) / sizeof(CTParagraphStyleSetting));
// 各設定を格納
NSMutableDictionary *attrDict = [@{
(id)kCTFontAttributeName : (__bridge id)fontRef,
(id)kCTGlyphInfoAttributeName : (__bridge id)glyphInfo,
(id)kCTParagraphStyleAttributeName : (__bridge id)paragraphStyle,
(id)kCTKernAttributeName : @(self.letterSpace), // 文字間
(id)kCTLigatureAttributeName : @(YES), // 合字
(id)kCTVerticalFormsAttributeName : @(value) // 縦書き準備 1/3
} mutableCopy];
CFRelease(glyphInfo);
CFRelease(paragraphStyle);
return attrDict;
}
//////////////////////////////////////////////////////////////
#pragma mark - Public
//////////////////////////////////////////////////////////////
- (void)toggleFontName
{
if ([self.fontName isEqualToString:@"HiraMinProN-W3"]) {
self.fontName = @"HiraKakuProN-W3";
}
else {
self.fontName = @"HiraMinProN-W3";
}
[self setNeedsDisplay];
}
- (NSRange)visibleRangeWithString:(NSString *)aString andFont:(UIFont *)aFont
{
CTFontRef font = CTFontCreateWithName((__bridge CFStringRef)aFont.fontName, aFont.pointSize, NULL);
NSMutableDictionary *attrDict = [self getAttributedStringSourceWithString:(__bridge CFStringRef)aString andFont:font andiSJP_ZH:YES];
NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:aString attributes:attrDict];
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)attrString);
CGMutablePathRef path = CGPathCreateMutable();
CGSize pathSize = self.bounds.size;
CGFloat reversingDiff = DEVICE_OS_MAJOR_VERSION <= 5 ? pathSize.height - pathSize.width : 0;
CGPathAddRect(path, NULL, CGRectMake(-reversingDiff, reversingDiff, BELOW_IOS_5_REVERSE_SIZE_WIDTH(pathSize), BELOW_IOS_5_REVERSE_SIZE_HEIGHT(pathSize)));
CFRange fitRange;
CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0, 0), NULL, CGSizeMake(BELOW_IOS_5_REVERSE_SIZE_WIDTH(pathSize), BELOW_IOS_5_REVERSE_SIZE_HEIGHT(pathSize)), &fitRange);
NSDictionary *frameDict = @{ (id)kCTFrameProgressionAttributeName : @(kCTFrameProgressionRightToLeft) };
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, fitRange, path, (__bridge CFDictionaryRef)frameDict);
CFRange fcatRange = CTFrameGetVisibleStringRange(frame);
CGPathRelease(path);
CFRelease(font);
CFRelease(framesetter);
CFRelease(frame);
return NSMakeRange(fcatRange.location, fcatRange.length);
}
- (NSRange)visibleRangeWithString:(NSString *)text
{
UIFont *font = [UIFont fontWithName:self.fontName size:self.fontSize];
return [self visibleRangeWithString:text andFont:font];
}
- (NSRange)visibleRangeWithNormalText:(NSString *)text andTitleText:(NSString *)title
{
NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] init];
_titleForTextSpace = 30.0f;
CTFontRef titleFont = CTFontCreateWithName((__bridge CFStringRef)self.fontName, self.fontSize * _titleSizeRate, NULL);
NSMutableDictionary *titleAttrDict = [self getAttributedStringSourceWithString:(CFStringRef)title andFont:titleFont andiSJP_ZH:YES];
NSMutableAttributedString *titleAttrString = [[NSMutableAttributedString alloc] initWithString:title attributes:titleAttrDict];
[attrString appendAttributedString:titleAttrString];
_titleForTextSpace = 0;
CTFontRef font = CTFontCreateWithName((__bridge CFStringRef)self.fontName, self.fontSize, NULL);
NSMutableDictionary *textAttrDict = [self getAttributedStringSourceWithString:(__bridge CFStringRef)text andFont:font andiSJP_ZH:YES];
NSMutableAttributedString *textAttrString = [[NSMutableAttributedString alloc] initWithString:text attributes:textAttrDict];
[attrString appendAttributedString:textAttrString];
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)attrString);
CGMutablePathRef path = CGPathCreateMutable();
CGSize pathSize = self.bounds.size;
CGFloat reversingDiff = DEVICE_OS_MAJOR_VERSION <= 5 ? pathSize.height - pathSize.width : 0;
CGPathAddRect(path, NULL, CGRectMake(-reversingDiff, reversingDiff, BELOW_IOS_5_REVERSE_SIZE_WIDTH(pathSize), BELOW_IOS_5_REVERSE_SIZE_HEIGHT(pathSize)));
CFRange fitRange = CFRangeMake(0, 0);
CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0, 0), NULL, CGSizeMake(BELOW_IOS_5_REVERSE_SIZE_WIDTH(pathSize), BELOW_IOS_5_REVERSE_SIZE_HEIGHT(pathSize)), &fitRange);
NSDictionary *frameDict = @{ (id)kCTFrameProgressionAttributeName : @(kCTFrameProgressionRightToLeft) };
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, fitRange, path, (__bridge CFDictionaryRef)frameDict);
CFRange cfRange = CTFrameGetVisibleStringRange(frame);
CGPathRelease(path);
CFRelease(titleFont);
CFRelease(font);
CFRelease(framesetter);
CFRelease(frame);
return NSMakeRange(cfRange.location, cfRange.length);
}
- (NSArray *)clusterVisibleRangesWithString:(NSString *)text startRange:(NSRange)startRange
{
CTFontRef font = CTFontCreateWithName((__bridge CFStringRef)self.fontName, self.fontSize, NULL);
NSMutableDictionary *attrDict = [self getAttributedStringSourceWithString:(__bridge CFStringRef)text andFont:font andiSJP_ZH:YES];
NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:text attributes:attrDict];
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)attrString);
CGMutablePathRef path = CGPathCreateMutable();
CGSize pathSize = self.bounds.size;
CGFloat reversingDiff = DEVICE_OS_MAJOR_VERSION <= 5 ? pathSize.height - pathSize.width : 0;
CGPathAddRect(path, NULL, CGRectMake(-reversingDiff, reversingDiff, BELOW_IOS_5_REVERSE_SIZE_WIDTH(pathSize), BELOW_IOS_5_REVERSE_SIZE_HEIGHT(pathSize)));
NSMutableArray *clusterRanges = [NSMutableArray array];
// [clusterRanges addObject:[NSValue valueWithRange:startRange]];
CFIndex location = startRange.length;
CFIndex length = [text length];
while (location < length) {
CFRange stringRange = CFRangeMake(location, length-location);
CFRange fitRange;
CTFramesetterSuggestFrameSizeWithConstraints(framesetter, stringRange, NULL, CGSizeMake(BELOW_IOS_5_REVERSE_SIZE_WIDTH(pathSize), BELOW_IOS_5_REVERSE_SIZE_HEIGHT(pathSize)), &fitRange);
NSDictionary *frameDict = @{ (id)kCTFrameProgressionAttributeName : @(kCTFrameProgressionRightToLeft) };
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, fitRange, path, (__bridge CFDictionaryRef)frameDict);
CFRange factRange = CTFrameGetVisibleStringRange(frame);
NSRange range = NSMakeRange(location, factRange.length);
[clusterRanges addObject:[NSValue valueWithRange:range]];
location += factRange.length;
CFRelease(frame);
}
CGPathRelease(path);
CFRelease(font);
CFRelease(framesetter);
return clusterRanges;
}
- (CGSize)linesSizeWithString:(NSString *)aString andFont:(UIFont *)aFont
{
CTFontRef font = CTFontCreateWithName((__bridge CFStringRef)aFont.fontName, aFont.pointSize, NULL);
NSMutableDictionary *attrDict = [self getAttributedStringSourceWithString:(CFStringRef)aString andFont:font andiSJP_ZH:YES];
NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:self.text attributes:attrDict];
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)attrString);
//
CGSize constraints = CGSizeMake(self.bounds.size.height, CGFLOAT_MAX);
CGSize size = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0, 0), NULL, constraints, NULL);
CFRelease(font);
CFRelease(framesetter);
return size;
}
- (CGSize)linesSizeWithTextString:(NSString *)text
{
UIFont *font = [UIFont fontWithName:self.fontName size:self.fontSize];
return [self linesSizeWithString:text andFont:font];
}
- (CGSize)titleLinesSizeWithTitleTextString:(NSString *)titleText
{
UIFont *font = [UIFont fontWithName:self.fontName size:self.fontSize * _titleSizeRate];
CGSize size = [self linesSizeWithString:titleText andFont:font];
size.height -= _titleForTextSpace * 2;
return size;
}
- (CGFloat)findHeightForText:(NSString *)text
{
CGFloat result = [UIFont fontWithName:self.fontName size:self.fontSize].pointSize+4;
// CGFloat width = 10000;
if (text) {
// CGSize textSize = { width, CGFLOAT_MAX }; //Width and height of text area
CGSize size;
CGRect frame = [text boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:@{NSFontAttributeName:[UIFont fontWithName:self.fontName size:self.fontSize]}
context:nil];
size = CGSizeMake(frame.size.width, frame.size.height+1);
result = MAX(size.height, result); //At least one row
}
return result;
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment