Permalink
Browse files

(Sponsored by AntiLoop.com) Reimplemented typesetting with manual ty…

…pesetter. This makes it unnecessary to do a separate correction pass and also works with absolute and relative line heights.
  • Loading branch information...
1 parent ba86004 commit 1f34f29f3fcfb8d9faad7c60a0a97544fe3d2415 @odrobnik odrobnik committed Feb 21, 2012
@@ -102,8 +102,142 @@ - (NSString *)description
return [self.lines description];
}
-- (void)buildLines
+#pragma mark Building the Lines
+
+// returns the head indent for this line, firstLine = YES for the first line in a paragraph, NO for subsequent lines
+- (CGFloat)_calculatedIndentAtIndex:(NSUInteger)index isFirstLineInParagraph:(BOOL)firstLine
+{
+ CTParagraphStyleRef paragraphStyle = (__bridge CTParagraphStyleRef)[_attributedStringFragment attribute:(id)kCTParagraphStyleAttributeName atIndex:index effectiveRange:NULL];
+
+ CGFloat indent = 0;
+
+ if (firstLine)
+ {
+ CTParagraphStyleGetValueForSpecifier(paragraphStyle, kCTParagraphStyleSpecifierFirstLineHeadIndent, sizeof(indent), &indent);
+ }
+ else
+ {
+ CTParagraphStyleGetValueForSpecifier(paragraphStyle, kCTParagraphStyleSpecifierHeadIndent, sizeof(indent), &indent);
+ }
+
+ return indent;
+}
+
+
+
+/* Builds the array of lines with the internal typesetter of our framesetter. No need to correct line origins in this case because they are placed correctly in the first place.
+ */
+- (void)_buildLinesWithTypesetter
+{
+ // only build lines if frame is legal
+ if (_frame.size.width<=0)
+ {
+ return;
+ }
+
+ // framesetter keeps internal reference, no need to retain
+ CTTypesetterRef typesetter = CTFramesetterGetTypesetter(_framesetter);
+
+ CFIndex lastIndex = [_attributedStringFragment length];
+
+ CFRange lineRange = CFRangeMake(0, 0);
+ NSMutableArray *typesetLines = [NSMutableArray array];
+
+ CGPoint lineOrigin = _frame.origin;
+
+ DTCoreTextLayoutLine *previousLine = nil;
+
+ // need the paragraph ranges to know if a line is at the beginning of paragraph
+ NSMutableArray *paragraphRanges = [[self paragraphRanges] mutableCopy];
+
+ NSRange currentParagraphRange = [[paragraphRanges objectAtIndex:0] rangeValue];
@siuying
siuying May 20, 2013

when self.paragraphRanges return empty array, this line crash (attributeString is empty string)

+
+ do
+ {
+ while (lineRange.location >= (currentParagraphRange.location+currentParagraphRange.length))
+ {
+ // we are outside of this paragraph, so we go to the next
+ [paragraphRanges removeObjectAtIndex:0];
+
+ currentParagraphRange = [[paragraphRanges objectAtIndex:0] rangeValue];
+ }
+
+ BOOL isAtBeginOfParagraph = (currentParagraphRange.location == lineRange.location);
+
+
+ CGFloat offset = [self _calculatedIndentAtIndex:lineRange.location isFirstLineInParagraph:isAtBeginOfParagraph];
+ lineOrigin.x = offset + _frame.origin.x;
+
+ // find how many characters we get into this line
+ lineRange.length = CTTypesetterSuggestLineBreakWithOffset(typesetter, lineRange.location, _frame.size.width - offset, offset);
+
+ // create a line to fit
+ CTLineRef line = CTTypesetterCreateLine(typesetter, lineRange);
+
+ // wrap it
+ DTCoreTextLayoutLine *newLine = [[DTCoreTextLayoutLine alloc] initWithLine:line layoutFrame:self];
+ CFRelease(line);
+
+ // get line height in px if it is specified for this line
+ CGFloat lineHeight = [newLine calculatedLineHeight];
+
+ // get the correct baseline origin
+ if (previousLine)
+ {
+ if (lineHeight==0)
+ {
+ lineHeight = previousLine.descent + newLine.ascent;
+ }
+
+ lineHeight += [previousLine paragraphSpacing:YES] + [newLine calculatedLeading];
+ }
+ else
+ {
+ if (lineHeight>0)
+ {
+ if (lineHeight<newLine.ascent)
+ {
+ // special case, we fake it to look like CoreText
+ lineHeight -= newLine.descent;
+ }
+ }
+ else
+ {
+ lineHeight = newLine.ascent;
+ }
+ }
+
+ lineOrigin.y += lineHeight;
+
+ newLine.baselineOrigin = lineOrigin;
+ [typesetLines addObject:newLine];
+
+ lineRange.location += lineRange.length;
+
+ previousLine = newLine;
+ }
+ while (lineRange.location < lastIndex);
+
+ _lines = typesetLines;
+
+ // at this point we can correct the frame if it is open-ended
+ if ([_lines count] && _frame.size.height == CGFLOAT_OPEN_HEIGHT)
+ {
+ // actual frame is spanned between first and last lines
+ DTCoreTextLayoutLine *lastLine = [_lines lastObject];
+
+ _frame.size.height = ceilf((CGRectGetMaxY(lastLine.frame) - _frame.origin.y + 1.5f));
+ }
+}
+
+
+
+- (void)_buildLines
{
+ // replaced previous approach with manual type setting
+ [self _buildLinesWithTypesetter];
+ return;
+/*
// get lines (don't own it so no release)
CFArrayRef cflines = CTFrameGetLines(_textFrame);
@@ -127,7 +261,8 @@ - (void)buildLines
lineOrigin.y = _frame.size.height - lineOrigin.y + _frame.origin.y;
lineOrigin.x += _frame.origin.x;
- DTCoreTextLayoutLine *newLine = [[DTCoreTextLayoutLine alloc] initWithLine:(__bridge CTLineRef)oneLine layoutFrame:self origin:lineOrigin];
+ DTCoreTextLayoutLine *newLine = [[DTCoreTextLayoutLine alloc] initWithLine:(__bridge CTLineRef)oneLine layoutFrame:self];
+ newLine.baselineOrigin = lineOrigin;
[tmpLines addObject:newLine];
@@ -138,7 +273,7 @@ - (void)buildLines
_lines = tmpLines;
// line origins are wrong on last line of paragraphs
- [self correctLineOrigins];
+ //[self correctLineOrigins];
// --- begin workaround for image squishing bug in iOS < 4.2
@@ -157,13 +292,14 @@ - (void)buildLines
_frame.size.height = ceilf((CGRectGetMaxY(lastLine.frame) - _frame.origin.y + 1.5f));
}
+ */
}
- (NSArray *)lines
{
if (!_lines)
{
- [self buildLines];
+ [self _buildLines];
}
return _lines;
@@ -622,7 +758,7 @@ - (CGRect)frame
{
if (_frame.size.height == CGFLOAT_OPEN_HEIGHT && !_lines)
{
- [self buildLines]; // corrects frame if open-ended
+ [self _buildLines]; // corrects frame if open-ended
}
if (![self.lines count])
@@ -631,15 +767,6 @@ - (CGRect)frame
}
return _frame;
- //
- // // actual frame is spanned between first and last lines
- // DTCoreTextLayoutLine *firstLine = [self.lines objectAtIndex:0];
- // DTCoreTextLayoutLine *lastLine = [self.lines lastObject];
- //
- // CGPoint origin = CGPointMake(roundf(firstLine.frame.origin.x), roundf(firstLine.frame.origin.y));
- // CGSize size = CGSizeMake(_frame.size.width, roundf(CGRectGetMaxY(lastLine.frame) - firstLine.frame.origin.y + 1));
- //
- // return (CGRect){origin, size};
}
- (DTCoreTextLayoutLine *)lineContainingIndex:(NSUInteger)index
@@ -16,7 +16,7 @@
NSInteger _stringLocationOffset; // offset to modify internal string location to get actual location
}
-- (id)initWithLine:(CTLineRef)line layoutFrame:(DTCoreTextLayoutFrame *)layoutFrame origin:(CGPoint)origin;
+- (id)initWithLine:(CTLineRef)line layoutFrame:(DTCoreTextLayoutFrame *)layoutFrame;
- (NSRange)stringRange;
- (NSInteger)numberOfGlyphs;
@@ -29,11 +29,32 @@
- (NSInteger)stringIndexForPosition:(CGPoint)position;
- (CGFloat)paragraphSpacing:(BOOL)zeroNonLast;
- (CGFloat)paragraphSpacing;
+
+
- (CGFloat)calculatedLineHeightMultiplier;
+
+/** Calculates the leading size which is the space before this current line.
+
+ @returns The leading for this line.
+ */
- (CGFloat)calculatedLeading;
+
+/** Calculates the line height for the entire line from going through the paragraph styles and finding minimum and maximum.
+
+ @returns The overall line height for this line or zero if no line height is specified.
+ */
+- (CGFloat)calculatedLineHeight;
+
- (void)drawInContext:(CGContextRef)context;
+
+/** Adjust the baselines of all lines in this layout frame to fit the heights of text attachments.
+
+ This is used to work around a CoreText bug that was fixed in iOS 4.2
+
+ @returns `YES` if the line needed an adjustment, `NO` if no adjustment was carried out
+ */
- (BOOL)correctAttachmentHeights:(CGFloat *)downShift;
@property (nonatomic, assign) CGRect frame;
@@ -44,7 +44,7 @@ @implementation DTCoreTextLayoutLine
@synthesize layoutLock;
-- (id)initWithLine:(CTLineRef)line layoutFrame:(DTCoreTextLayoutFrame *)layoutFrame origin:(CGPoint)origin;
+- (id)initWithLine:(CTLineRef)line layoutFrame:(DTCoreTextLayoutFrame *)layoutFrame
{
if ((self = [super init]))
{
@@ -54,7 +54,6 @@ - (id)initWithLine:(CTLineRef)line layoutFrame:(DTCoreTextLayoutFrame *)layoutFr
NSAttributedString *globalString = [layoutFrame attributedStringFragment];
_attributedString = [[globalString attributedSubstringFromRange:[self stringRange]] copy];
- _baselineOrigin = origin;
layoutLock = dispatch_semaphore_create(1);
}
return self;
@@ -381,6 +380,37 @@ - (CGFloat)calculatedLeading
return maxLeading;
}
+
+// returns line height if it is specified in any paragraph style in this line, zero if not specified
+- (CGFloat)calculatedLineHeight
+{
+ NSRange range = NSMakeRange(0, [_attributedString length]);
+
+ __block float lineHeight = 0;
+
+ [_attributedString enumerateAttribute:(id)kCTParagraphStyleAttributeName inRange:range options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired
+ usingBlock:^(id value, NSRange range, BOOL *stop) {
+ CTParagraphStyleRef paragraphStyle = (__bridge CTParagraphStyleRef)value;
+
+ CGFloat minimumLineHeight = 0;
+ CGFloat maximumLineHeight = 0;
+
+ CTParagraphStyleGetValueForSpecifier(paragraphStyle, kCTParagraphStyleSpecifierMinimumLineHeight, sizeof(minimumLineHeight), &minimumLineHeight);
+ CTParagraphStyleGetValueForSpecifier(paragraphStyle, kCTParagraphStyleSpecifierMaximumLineHeight, sizeof(maximumLineHeight), &maximumLineHeight);
+
+ if (lineHeight<minimumLineHeight)
+ {
+ lineHeight = minimumLineHeight;
+ }
+
+ if (maximumLineHeight>0 && lineHeight>maximumLineHeight)
+ {
+ lineHeight = maximumLineHeight;
+ }
+ }];
+ return lineHeight;
+}
+
#pragma mark Properties
- (NSArray *)glyphRuns
{
@@ -635,16 +635,18 @@ - (void)applyStyleDictionary:(NSDictionary *)styles
if ([lineHeight isEqualToString:@"normal"])
{
self.paragraphStyle.lineHeightMultiple = 0.0; // default
+ self.paragraphStyle.minimumLineHeight = 0.0; // default
+ self.paragraphStyle.maximumLineHeight = 0.0; // default
}
else if ([lineHeight isEqualToString:@"inherit"])
{
// no op, we already inherited it
}
else if ([lineHeight isNumeric])
{
- self.paragraphStyle.lineHeightMultiple = [lineHeight floatValue];
- // self.paragraphStyle.minimumLineHeight = fontDescriptor.pointSize * (CGFloat)[lineHeight intValue];
- // self.paragraphStyle.maximumLineHeight = self.paragraphStyle.minimumLineHeight;
+ //self.paragraphStyle.lineHeightMultiple = [lineHeight floatValue];
+ self.paragraphStyle.minimumLineHeight = fontDescriptor.pointSize * (CGFloat)[lineHeight intValue];
+ self.paragraphStyle.maximumLineHeight = self.paragraphStyle.minimumLineHeight;
}
else // interpret as length
{
@@ -247,6 +247,8 @@
A7949AA314CC5BDF00A8CCDE /* DTHTMLAttributedStringBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = A7949A4614CAF58C00A8CCDE /* DTHTMLAttributedStringBuilder.m */; };
A7949AA514CD3C5D00A8CCDE /* DTHTMLParser.m in Sources */ = {isa = PBXBuildFile; fileRef = A7949A4914CAF5A300A8CCDE /* DTHTMLParser.m */; };
A7949AA614CD3C5E00A8CCDE /* DTHTMLParser.m in Sources */ = {isa = PBXBuildFile; fileRef = A7949A4914CAF5A300A8CCDE /* DTHTMLParser.m */; };
+ A7A95D9514F3F45E002E3F7E /* LineHeight.html in Resources */ = {isa = PBXBuildFile; fileRef = A7A95D9414F3F45E002E3F7E /* LineHeight.html */; };
+ A7A95D9714F3F496002E3F7E /* LineHeight.html in Resources */ = {isa = PBXBuildFile; fileRef = A7A95D9414F3F45E002E3F7E /* LineHeight.html */; };
A7B0B56814D9921F0091C2C9 /* NSAttributedString+DTCoreText.h in Headers */ = {isa = PBXBuildFile; fileRef = A7B0B56614D9921F0091C2C9 /* NSAttributedString+DTCoreText.h */; };
A7B0B56914D9921F0091C2C9 /* NSAttributedString+DTCoreText.h in Headers */ = {isa = PBXBuildFile; fileRef = A7B0B56614D9921F0091C2C9 /* NSAttributedString+DTCoreText.h */; };
A7B0B56A14D9921F0091C2C9 /* NSAttributedString+DTCoreText.m in Sources */ = {isa = PBXBuildFile; fileRef = A7B0B56714D9921F0091C2C9 /* NSAttributedString+DTCoreText.m */; };
@@ -501,6 +503,7 @@
A7949A6D14CC45B400A8CCDE /* MacUnitTest-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "MacUnitTest-Info.plist"; sourceTree = "<group>"; };
A7949A6E14CC45B500A8CCDE /* MacUnitTest-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MacUnitTest-Prefix.pch"; sourceTree = "<group>"; };
A7949A9C14CC565F00A8CCDE /* DTCoreTextConstants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = DTCoreTextConstants.h; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; };
+ A7A95D9414F3F45E002E3F7E /* LineHeight.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = LineHeight.html; sourceTree = "<group>"; };
A7B0B56614D9921F0091C2C9 /* NSAttributedString+DTCoreText.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSAttributedString+DTCoreText.h"; sourceTree = "<group>"; };
A7B0B56714D9921F0091C2C9 /* NSAttributedString+DTCoreText.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSAttributedString+DTCoreText.m"; sourceTree = "<group>"; };
A7C1CC0E14D6CFD5008D6468 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = SDKs/MacOSX10.7.sdk/System/Library/Frameworks/AppKit.framework; sourceTree = DEVELOPER_DIR; };
@@ -764,6 +767,7 @@
A788CA2F14863EF100E1AFD9 /* XB Niloofar.ttf */,
A788CA3014863EF100E1AFD9 /* XB NiloofarBd.ttf */,
A7D54EA414E003190063E78B /* Objects.html */,
+ A7A95D9414F3F45E002E3F7E /* LineHeight.html */,
);
path = Resources;
sourceTree = "<group>";
@@ -1159,6 +1163,7 @@
A788CA4E14863EF100E1AFD9 /* XB Niloofar.ttf in Resources */,
A788CA4F14863EF100E1AFD9 /* XB NiloofarBd.ttf in Resources */,
A7D54EA514E003190063E78B /* Objects.html in Resources */,
+ A7A95D9514F3F45E002E3F7E /* LineHeight.html in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1203,6 +1208,7 @@
A74C973E14DC5294002B5A45 /* Emoji.html in Resources */,
A71B927114E0165000360C30 /* PreWhitespace.html in Resources */,
A71B927314E016A300360C30 /* PreWhitespace.plist in Resources */,
+ A7A95D9714F3F496002E3F7E /* LineHeight.html in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1246,7 +1252,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "/usr/local/bin/appledoc \\\n--project-name \"DTCoreText\" \\\n--project-company \"Cocoanetics\" \\\n--company-id \"com.cocoanetics\" \\\n--docset-atom-filename \"DTCoreText\" \\\n--docset-feed-url \"http://cocoanetics.github.com/DTCoreText/%DOCSETATOMFILENAME\" \\\n--docset-package-url \"http://cocoanetics.github.com/DTCoreText/%DOCSETPACKAGEFILENAME\" \\\n--docset-fallback-url \"http://cocoanetics.github.com/DTCoreText/\" \\\n--output \"~/help/DTCoreText\" \\\n--publish-docset \\\n--logformat xcode \\\n--keep-undocumented-objects \\\n--keep-undocumented-members \\\n--keep-intermediate-files \\\n--no-repeat-first-par \\\n--no-warn-invalid-crossref \\\n--ignore \"*.m\" \\\n--ignore \"LoadableCategory.h\" \\\n--ignore \"*Test.h\" \\\n--ignore \"Demo*.h\" \\\n--index-desc \"${PROJECT_DIR}/readme.markdown\" \\\n\"${PROJECT_DIR}\"";
+ shellScript = "/usr/local/bin/appledoc \\\n--project-name \"DTCoreText\" \\\n--project-company \"Cocoanetics\" \\\n--company-id \"com.cocoanetics\" \\\n--docset-atom-filename \"DTCoreText\" \\\n--docset-feed-url \"http://cocoanetics.github.com/DTCoreText/%DOCSETATOMFILENAME\" \\\n--docset-package-url \"http://cocoanetics.github.com/DTCoreText/%DOCSETPACKAGEFILENAME\" \\\n--docset-fallback-url \"http://cocoanetics.github.com/DTCoreText/\" \\\n--output \"~/help/DTCoreText\" \\\n--publish-docset \\\n--logformat xcode \\\n--keep-intermediate-files \\\n--keep-undocumented-members \\\n--keep-undocumented-objects \\\n--no-repeat-first-par \\\n--no-warn-invalid-crossref \\\n--ignore \"*.m\" \\\n--index-desc \"${PROJECT_DIR}/readme.markdown\" \\\n\"${PROJECT_DIR}/Core/Source\"";
};
A788CA661486456100E1AFD9 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
@@ -0,0 +1,18 @@
+<h1 style="color:blue">Line Height</h1>
+<p>This demonstrates setting of different line heights. Note: Internally this is achieved by setting the minimum and maximum line height in the paragraph style. The paragraph style line height multiplier is ignored.</p>
+<h3 style="color:red">Relative Line Heights</h3>
+<p>Font Size 25px; Line Height: normal</p>
+<p style="font-size:25px;line-height:normal">Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg</p>
+<p>Font Size 25px; Line Height: 150%</p>
+<p style="font-size:25px;line-height:150%">Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg</p>
+<p>Font Size 25px; Line Height: 2</p>
+<p style="font-size:25px;line-height:2">Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg</p>
+
+<h3 style="color:red">Absolut Line Heights</h3>
+
+<p>Font Size 25px; Line Height: 15px</p>
+<p style="font-size:25px;line-height:15px">Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg</p>
+<p>Font Size 25px; Line Height: 25px</p>
+<p style="font-size:25px;line-height:25px">Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg</p>
+<p>Font Size 25px; Line Height: 40px</p>
+<p style="font-size:25px;line-height:40px">Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg Xg</p>
Oops, something went wrong. Retry.

0 comments on commit 1f34f29

Please sign in to comment.