前言

第二篇中提到,TextKit的分页效率太低,但是用基于TextKit回炉的UITextView来显示文字的效率还是比较好的,所以可以考虑用CoreText来分页,用TextKit显示结果,关键在于统一两者使用的字体,文本布局,在UIKit的NSAttribuedString.h中定义了大量的键,这里我们可以调整所有可用的属性,如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
 NSFontAttributeName NS_AVAILABLE_IOS(6_0); 
<span style="color: #ff0000;">// UIFont, default Helvetica(Neue) 12</span>

 NSParagraphStyleAttributeName NS_AVAILABLE_IOS(6_0); 
<span style="color: #ff0000;">// NSParagraphStyle, default defaultParagraphStyle</span>

 NSForegroundColorAttributeName NS_AVAILABLE_IOS(6_0); 
<span style="color: #ff0000;">// UIColor, default blackColor</span>

 NSBackgroundColorAttributeName NS_AVAILABLE_IOS(6_0); 
<span style="color: #ff0000;">// UIColor, default nil: no background</span>

 NSLigatureAttributeName NS_AVAILABLE_IOS(6_0); 
<span style="color: #ff0000;">// NSNumber containing integer, default 1: default ligatures, 0: no ligatures</span>

 NSKernAttributeName NS_AVAILABLE_IOS(6_0); 
<span style="color: #ff0000;">// NSNumber containing floating point value, in points; amount to modify default kerning. 0 means kerning is disabled.</span>

 NSStrikethroughStyleAttributeName NS_AVAILABLE_IOS(6_0); 
<span style="color: #ff0000;">// NSNumber containing integer, default 0: no strikethrough</span>

 NSUnderlineStyleAttributeName NS_AVAILABLE_IOS(6_0); 
<span style="color: #ff0000;">// NSNumber containing integer, default 0: no underline</span>

 NSStrokeColorAttributeName NS_AVAILABLE_IOS(6_0); 
<span style="color: #ff0000;">// UIColor, default nil: same as foreground color</span>

 NSStrokeWidthAttributeName NS_AVAILABLE_IOS(6_0); 
<span style="color: #ff0000;">// NSNumber containing floating point value, in percent of font point size, default 0: no stroke; positive for stroke alone, negative for stroke and fill (a typical value for outlined text would be 3.0)</span>

 NSShadowAttributeName NS_AVAILABLE_IOS(6_0); 
<span style="color: #ff0000;">// NSShadow, default nil: no shadow</span>

NSTextEffectAttributeName NS_AVAILABLE_IOS(7_0); 
<span style="color: #ff0000;">// NSString, default nil: no text effect</span>

NSAttachmentAttributeName NS_AVAILABLE_IOS(7_0); 
<span style="color: #ff0000;">// NSTextAttachment, default nil</span>

NSLinkAttributeName NS_AVAILABLE_IOS(7_0); 
<span style="color: #ff0000;">// NSURL (preferred) or NSString</span>

NSBaselineOffsetAttributeName NS_AVAILABLE_IOS(7_0);
<span style="color: #ff0000;">// NSNumber containing floating point value, in points; offset from baseline, default 0</span>

NSUnderlineColorAttributeName NS_AVAILABLE_IOS(7_0); 
<span style="color: #ff0000;">// UIColor, default nil: same as foreground color</span>

NSStrikethroughColorAttributeName NS_AVAILABLE_IOS(7_0); 
<span style="color: #ff0000;">// UIColor, default nil: same as foreground color</span>

NSObliquenessAttributeName NS_AVAILABLE_IOS(7_0); 
<span style="color: #ff0000;">// NSNumber containing floating point value; skew to be applied to glyphs, default 0: no skew</span>

NSExpansionAttributeName NS_AVAILABLE_IOS(7_0); 
<span style="color: #ff0000;">// NSNumber containing floating point value; log of expansion factor to be applied to glyphs, default 0: no expansion</span>

NSWritingDirectionAttributeName NS_AVAILABLE_IOS(7_0); 
<span style="color: #ff0000;">// NSArray of NSNumbers representing the nested levels of writing direction overrides as defined by Unicode LRE, RLE, LRO, and RLO characters. The control characters can be obtained by masking NSWritingDirection and NSTextWritingDirection values. LRE: NSWritingDirectionLeftToRight|NSTextWritingDirectionEmbedding, RLE: NSWritingDirectionRightToLeft|NSTextWritingDirectionEmbedding, LRO: NSWritingDirectionLeftToRight|NSTextWritingDirectionOverride, RLO: NSWritingDirectionRightToLeft|NSTextWritingDirectionOverride,</span>

 NSVerticalGlyphFormAttributeName NS_AVAILABLE_IOS(6_0); 
<span style="color: #ff0000;">// An NSNumber containing an integer value. 0 means horizontal text. 1 indicates vertical text. If not specified, it could follow higher-level vertical orientation settings. Currently on iOS, it's always horizontal. The behavior for any other value is undefined.
</span>

由于我的目标是完善阅读器的分页,所以需要考虑的只有字体样式,段落样式,将UITextView的

textContainerInset置零,并将textContainer的lineFragmentPadding置零,然后其余都可以按照默认设置,首先来看看段落样式。

NSParagraphStyle

iOS中各种对象的都拥有默认值,其中NSParagraphStyle的默认值如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Alignment 4,对齐方式,使用默认对齐方式,也就是向左对齐
LineSpacing 0,行间距,默认为0,This value is included in the line fragment heights in layout manager.
ParagraphSpacing 0, 段落间距:本段落末尾与下一段落顶部的距离,默认为0
ParagraphSpacingBefore 0, 段落间距:本段落的顶部与上一段落的底部的距离,默认为0
HeadIndent 0, 每行首部与给定margin的距离
TailIndent 0, 每行尾部与给定margin的距离
FirstLineHeadIndent 0, 段落首行与给定margin的距离,即首行缩进
LineHeight 0/0, 
LineHeightMultiple 0, 
LineBreakMode 0, 断行方式,默认按字断行,也就是一次填充一个汉字或单词,剩余空间无法容纳是就断行
Tabs (
    28L,
    56L,
    84L,
    112L,
    140L,
    168L,
    196L,
    224L,
    252L,
    280L,
    308L,
    336L
), 
DefaultTabInterval 0, 
Blocks (null), 
Lists (null), 
BaseWritingDirection -1, 默认书写方式,按照语言规则,中英文是从左到右
HyphenationFactor 0, 连字符号因素Specifies the threshold for hyphenation,默认为0,使用layout manger来管理连字符号使用,而它的HyphenationFactor默认也为0,不启用连写字符。
TighteningFactor 0, 
HeaderLevel 0

NSParagraphStyle的可变子类NSMutableParagraphStyle可以修改各项参数。

UIFontDescriptor

做了几个测试后,发现这个对我完全没有什么卵用。

使用默认方法生成字体对象就好。

其他

有些控件会自己调整行距,比如UITextView,而CATextLayer也有自己的段落样式,同一段由CoreText产生的分页结果,直接提供给CATextLayer基本上会溢出边框,它适合少量的文本,比如在UILabel上添加一层自定义的文本,而使用NSString在上下文中绘图,然后获取位图,传递给CALayer的content则不会发生溢出下一篇,我们记录下各种分页并呈现文字的方式。

第二篇中提到的测试

接下来补上第二篇中有关leading,lineHeight,文本行高度的测试

新建一个SingleView Application,在ViewController中插入以下代码,我使用的是iPhone5 模拟器,iOS8.3,Xcode 6.3.1

1.创建文本,我使用了一本txt格式的小说,编码方式为UTF-8。

2.创建一个UITextView,设置内部textContainer的Inset为0,并设置linePadding为0,这样文本容器完整覆盖整个UITextView。

3.创建字体,分别使用了三种字体,第一种为中文字体,第二种为英文字体,第三种为系统字体,然后打印字体信息,创建属性化字符串后接着设置UITextView的文本内容,然后取一段包含中英文的文本段,打印内容,并计算指定位置的标记符号所在行的行高。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
-(void)viewDidLoad
{
 [super viewDidLoad];
 [self.view setBackgroundColor:[UIColor redColor]];
 NSString *aText = [NSString stringWithContentsOfFile:<span style="color: #ff0000;">_path</span> encoding:NSUTF8StringEncoding error:nil];
 CGRect textViewRect = CGRectMake(5, 8, 310, 560);
 UITextView *textView = [[UITextView alloc] initWithFrame:textViewRect];
 [textView setBackgroundColor:[UIColor colorWithRed:249.0/255 green:234.0/255 blue:188.0/255 alpha:1]];
 [textView setUserInteractionEnabled:NO];
 [textView.textContainer setLineFragmentPadding:0.0f];
 [textView setTextContainerInset:UIEdgeInsetsZero];

 UIFont *font = [UIFont fontWithName:@"STHeitiSC-Light" size:15];
 NSLog(@"%@",font);
 NSLog(@"\nfamilyName:%@ \nfontName:%@ \npointSize:%f \nascender:%f \ndescender:%f \ncapHeight:%f \nxHeight:%f \nlineHeight:%f \nleading:%f",font.familyName,font.fontName,font.pointSize,font.ascender,font.descender,font.capHeight,font.xHeight,font.lineHeight,font.leading);

 NSMutableDictionary *dic = [NSMutableDictionary dictionary];
 dic[NSFontAttributeName] = font;
 NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init];
 dic[NSParagraphStyleAttributeName] = style;
<span style="color: #ff0000;"> style.lineSpacing = 0.4f;</span>
 NSAttributedString *str = [[NSAttributedString alloc] initWithString:aText attributes:dic];
 NSLog(@"%@",[str attributedSubstringFromRange:NSMakeRange(0, 465)]);
 [textView setAttributedText:str];
 [self.view addSubview:textView];
 NSLog(@"%@",[textView.attributedText attributedSubstringFromRange:NSMakeRange(0, 465)]);
 NSLayoutManager *manager = textView.layoutManager;
 NSRange ra ;
 [self printfRect:[manager lineFragmentRectForGlyphAtIndex:445 effectiveRange:&ra]];


 font = [UIFont fontWithName:@"HelveticaNeue-Light" size:15];
 NSLog(@"\nfamilyName:%@ \nfontName:%@ \npointSize:%f \nascender:%f \ndescender:%f \ncapHeight:%f \nxHeight:%f \nlineHeight:%f \nleading:%f",font.familyName,font.fontName,font.pointSize,font.ascender,font.descender,font.capHeight,font.xHeight,font.lineHeight,font.leading);

 dic = [NSMutableDictionary dictionary];
 dic[NSFontAttributeName] = font;
 dic[NSParagraphStyleAttributeName] = style;
 str = [[NSAttributedString alloc] initWithString:aText attributes:dic];
 NSLog(@"%@",[str attributedSubstringFromRange:NSMakeRange(0, 465)]);
 [textView setAttributedText:str];
 NSLog(@"%@",[textView.attributedText attributedSubstringFromRange:NSMakeRange(0, 465)]);
 manager = textView.layoutManager;
 [self printfRect:[manager lineFragmentRectForGlyphAtIndex:445 effectiveRange:&ra]];

 font = [UIFont systemFontOfSize:15];
 NSLog(@"\nfamilyName:%@ \nfontName:%@ \npointSize:%f \nascender:%f \ndescender:%f \ncapHeight:%f \nxHeight:%f \nlineHeight:%f \nleading:%f",font.familyName,font.fontName,font.pointSize,font.ascender,font.descender,font.capHeight,font.xHeight,font.lineHeight,font.leading);
 dic = [NSMutableDictionary dictionary];
 dic[NSFontAttributeName] = font;
 dic[NSParagraphStyleAttributeName] = style;
 str = [[NSAttributedString alloc] initWithString:aText attributes:dic];
 NSLog(@"%@",[str attributedSubstringFromRange:NSMakeRange(0, 465)]);
 [textView setAttributedText:str];
 NSLog(@"%@",[textView.attributedText attributedSubstringFromRange:NSMakeRange(0, 465)]);
 manager = textView.layoutManager;
 [self printfRect:[manager lineFragmentRectForGlyphAtIndex:445 effectiveRange:&ra]];
}
-(void)printfRect:(CGRect)rect
{
 NSLog(@"x:%f y:%f width:%f height:%f",rect.origin.x
 ,rect.origin.y,rect.size.width,rect.size.height);
}
-(void)printLayout:(UIEdgeInsets) inset
{
 NSLog(@"right:%f left:%f top:%f bottom:%f",inset.right,inset.left,inset.top,inset.bottom);
}
-(BOOL)prefersStatusBarHidden
{
 return _isHidden;
}

当lineSpacing为0时,结果如下

lineSpacing

可以看到三种字体中,第一种包含字体最多,第二种最少,第三种次之,由于属性文本打印长度太大,这里暂且略过,它们的终端输出结果如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
2015-06-01 23:50:12.798 PureReader[6557:280430] 
familyName:Heiti SC 
fontName:STHeitiSC-Light 
pointSize:15.000000 
ascender:12.900000 
descender:-2.100000 
capHeight:11.670000 
xHeight:8.865000 
lineHeight:15.000000 
leading:15.000000
2015-06-01 23:50:12.820 PureReader[6557:280430] x:0.000000 y:154.499985 width:310.000000 height:15.450000
2015-06-01 23:50:12.821 PureReader[6557:280430] 
familyName:Helvetica Neue 
fontName:HelveticaNeue-Light 
pointSize:15.000000 
ascender:14.505000 
descender:-3.195000 
capHeight:10.920000 
xHeight:7.950000 
lineHeight:17.700001 
leading:17.700001
2015-06-01 23:50:12.858 PureReader[6557:280430] x:0.000000 y:181.349991 width:310.000000 height:18.135000
2015-06-01 23:50:12.858 PureReader[6557:280430] 
familyName:.Helvetica Neue Interface 
fontName:.HelveticaNeueInterface-Regular 
pointSize:15.000000 
ascender:14.280000 
descender:-3.615000 
capHeight:10.710000 
xHeight:7.755000 
lineHeight:17.895000 
leading:17.895000
2015-06-01 23:50:12.903 PureReader[6557:280430] x:0.000000 y:178.950027 width:310.000000 height:17.895000

可以参照第二篇,第一种,第二种字体的lineSpacing由系统计算,第三种字体没有默认lineSpacing,不支持中文的第二种,第三种字体使用了默认的中文字体,由于中英文混排,文本行高度取行高较大的字体,并以此对文本进行排版;

当字体大小为15时,如果调整lineSpacing,可以发现,lineSpacing小于0.45时对第一种字体不起作用,而对第三种字体起作用;

当保持lineSpacing为0,调整字体大小,可以发现,第一、第二种的文本行高与lineHeight不断加大,系统计算的lineSpacing发生变化,而第三种字体的lineSpacing保持不变。