Rich Text rendering using CoreText

Discussion in 'iPhone Tips, Help and Troubleshooting' started by arnieterm, Jul 23, 2010.

  1. arnieterm macrumors regular

    Joined:
    Aug 28, 2008
    #1
    Hi all
    I am in the process of rendering rich text using CoreText in iphone os sdk 4. The drawing can be single page or multi page. I have subclassed the UIView whose draw rect is as given below:

    Code:
    
    - (void)drawRect:(CGRect)rect{			
    	// Initialize a graphics context and set the text matrix to a known value.
    	CGContextRef context = UIGraphicsGetCurrentContext();		  
    	
    	float viewHeight = self.bounds.size.height;	
    	CGContextTranslateCTM(context, 0, viewHeight);
    	CGContextScaleCTM(context, 1.0, -1.0);
    	CGContextSetTextMatrix(context, CGAffineTransformIdentity);	 	
    	CGRect textBounds = rect;			
    	textBounds.origin.y = -10;
    	NSArray* arrayOfElements = [_delegate getArrayOfDataToRender];
    	int arrayIndex = 0;
    	for (arrayIndex = startIndex; arrayIndex < [arrayOfElements count]; arrayIndex++) {		
    		id currentElement = [arrayOfElements objectAtIndex:arrayIndex];		
    		if ( [currentElement isKindOfClass:[NSMutableAttributedString class]] ) {							
    			CFMutableAttributedStringRef attrString = NULL;
    			if (( startRange.location>0 ) && (arrayIndex == startIndex)) {					
    				attrString = (CFMutableAttributedStringRef) ([ ((NSAttributedString*)currentElement) attributedSubstringFromRange:startRange] );												
    			}else {
    				attrString = (CFMutableAttributedStringRef)currentElement;				
    			}	
    			CGMutablePathRef path = CGPathCreateMutable(); 			
    			CGPathAddRect(path, NULL, textBounds);			
    			CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(attrString);
    			CFRange fullAttributedStringRange = CFRangeMake(0, CFStringGetLength((CFStringRef)attrString));						
    			CTFrameRef frame = CTFramesetterCreateFrame(framesetter, fullAttributedStringRange, path, NULL);	
    			NSLog(@"Before drawing the available rectangle is :	%@",NSStringFromCGRect(textBounds));
    			if (frame != NULL) {
    				CTFrameDraw(frame, context);				
    				CFRange visibleRange = CTFrameGetVisibleStringRange(frame);	
    				if (visibleRange.length < fullAttributedStringRange.length) {							
    					NSRange invisibleStringRange = NSMakeRange(visibleRange.length , fullAttributedStringRange.length - visibleRange.length );					
    					CFRelease(frame);
    					frame = NULL;					
    					CFRelease(framesetter);					
    					framesetter = NULL;
    					CFRelease(path);	
    					path = NULL;
    					attrString = NULL;										
    					[_delegate insertNextPage: arrayIndex: invisibleStringRange];					   					
    					break;
    				}				
    				
    				if (arrayIndex == 5) {
    					NSLog(@"Text rendered	`:%@",[ ((NSMutableAttributedString*)attrString) string]);
    				}
    				CGFloat aHeight = [self measureFrame: frame: context];
                                     //I need to do the below given stuff in order to bring the various frames closer to each other. Not an applicable approach at all
    				if (CFArrayGetCount(CTFrameGetLines(frame)) >1) {
    					textBounds.origin.y -= (aHeight)/2;
    					textBounds.size.height -= (aHeight);	
    				}else {
    					textBounds.origin.y -= aHeight;			
    					textBounds.size.height -= (aHeight);	
    				}				
    				NSLog(@"Height is		:%f",aHeight);
    				NSLog(@"After drawing the available rectangle is :	%@",NSStringFromCGRect(textBounds));
    				NSLog(@"************************\n\n");
    				
    				CFRelease(frame);
    				frame = NULL;
    				CFRelease(framesetter);				
    				framesetter = NULL;
    				CFRelease(path);
    				path = NULL;
    				attrString = NULL;				
    			}			
    		}else if ([currentElement isKindOfClass:[UIImage class]]) {
    			/*
    			UIImage* currentImage = (UIImage*)currentElement;
    			CGSize imgSize = currentImage.size;
    			if (imgSize.height > textBounds.size.height) {
    				[_delegate insertNextPage: arrayIndex: NSMakeRange(0, 0)];					   					
    				break;
    			}
    			CGRect imageFrame = CGRectMake((textBounds.size.width - imgSize.width)/2, -(textBounds.origin.y ), imgSize.width, imgSize.height);			
    			UIImageView* imgvw = [ [ UIImageView alloc] initWithFrame: imageFrame];
    			imgvw.image = currentImage;
    			[self addSubview:imgvw];
    			[imgvw release];
    			textBounds.origin.y -= imgSize.height ;	
    			 */
    		}
    		
    	}
    	if (arrayIndex == [arrayOfElements count] ) {
    		[_delegate drawingFinished];		   
    	}
    }
    
    
    Here "_delegate" points to another class instance that initiates the process of rendering the rich text.
    My problem is that I am getting the height of rendered text using the function call measureFrame which is little bigger than the actual one which results in bigger gaps between paragraphs. Also it is quite confusing when it comes to dealing with coordinate system for CoreText in iphone. The function that is calulating the height of CTFrame is as given below

    Code:
    
    -(CGFloat)measureFrame: (CTFrameRef)frame : (CGContextRef)ctx{
    	CFArrayRef lines = CTFrameGetLines(frame);
    	CGFloat height = 0.0;
    	CGFloat height2 = 0.0;
    	CGFloat ascent, descent, leading;
    	for (CFIndex index = 0; index < CFArrayGetCount(lines); index++) {
    		CTLineRef line = (CTLineRef)CFArrayGetValueAtIndex(lines, index);
    		CTLineGetTypographicBounds(line, &ascent,  &descent, &leading);	
    		CGRect lineHeight = CTLineGetImageBounds(line, ctx);
    		height += lineHeight.size.height;
    		height2 += (ascent + fabsf(descent) + leading);		
    	}
    	NSLog(@"Height				%f			Height2		:%f",height, height2);
    	return ceil(height);
    
    
    
    I have also tried "CTLineGetTypographicBounds" instead of CTLineGetImageBounds and also considered the last line in each frame.
    Still getting bigger frame height.
    Whats wrong here?
    Any idea??
    Thanks
    Arnieterm
     
  2. arnieterm thread starter macrumors regular

    Joined:
    Aug 28, 2008
  3. arnieterm thread starter macrumors regular

    Joined:
    Aug 28, 2008
    #3
    Modified the function for measuring the height of CTFrame as below:

    Code:
    
    -(CGFloat)measureFrame: (CTFrameRef)frame{	
    	
    	CFArrayRef lines = CTFrameGetLines(frame);	
    	NSUInteger n = CFArrayGetCount(lines);
    	CTLineRef firstLine;	
    	CGFloat height = 0.0;
    	CGFloat asscent, descent, leading;
    	if (n == 1) {
    		firstLine = (CTLineRef)[(NSArray*)lines objectAtIndex:0];
    		CTLineGetTypographicBounds(firstLine, &asscent, &descent, &leading);
    		height = asscent + descent + leading;		
    		return height;
    	}
    	CFIndex numLines = CFArrayGetCount(lines);
    	CFIndex lastLineIndex = numLines - 1;	
    	for( CFIndex index = 0; index < numLines; index++){
    		CTLineRef line = (CTLineRef) CFArrayGetValueAtIndex(lines, index);
    		CTLineGetTypographicBounds(line, &asscent, &descent, &leading);
    		if (index != lastLineIndex) {
    			height += asscent + leading;
    		}else {
    			height += leading;
    		}
    	}
    	
    	return height;
    }
    
    It is working fine for me but I think it may require improvement
     

Share This Page