Merge pull request #436 from minggo/iss347

Iss347
This commit is contained in:
minggo 2011-08-01 03:29:24 -07:00
commit 6715ee03d7
30 changed files with 2727 additions and 107 deletions

View File

@ -83,6 +83,9 @@ public class Cocos2dxActivity extends Activity{
backgroundMusicPlayer = new Cocos2dxMusic(this);
soundPlayer = new Cocos2dxSound(this);
// init bitmap context
Cocos2dxBitmap.setContext(this);
handler = new Handler(){
public void handleMessage(Message msg){
switch(msg.what){

View File

@ -4,6 +4,7 @@ import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.LinkedList;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
@ -11,6 +12,7 @@ import android.graphics.Paint;
import android.graphics.Typeface;
import android.graphics.Paint.Align;
import android.graphics.Paint.FontMetricsInt;
import android.util.Log;
public class Cocos2dxBitmap{
/*
@ -21,6 +23,12 @@ public class Cocos2dxBitmap{
private static final int ALIGNLEFT = 0x31;
private static final int ALIGNRIGHT = 0x32;
private static Context context;
public static void setContext(Context context){
Cocos2dxBitmap.context = context;
}
/*
* @width: the width to draw, it can be 0
* @height: the height to draw, it can be 0
@ -244,8 +252,30 @@ public class Cocos2dxBitmap{
Paint paint = new Paint();
paint.setColor(Color.WHITE);
paint.setTextSize(fontSize);
paint.setTypeface(Typeface.create(fontName, Typeface.NORMAL));
paint.setAntiAlias(true);
Typeface typeFace = null;
/*
* Set type face for paint, now it support .ttf file.
*/
if (fontName.endsWith(".ttf")){
try {
typeFace = Typeface.createFromAsset(context.getAssets(), fontName);
} catch (Exception e){
Log.e("Cocos2dxBitmap",
"error to create ttf type face: " + fontName);
/*
* The file may not find, use system font
*/
paint.setTypeface(Typeface.create(fontName, Typeface.NORMAL));
}
}
else {
paint.setTypeface(Typeface.create(fontName, Typeface.NORMAL));
}
paint.setTypeface(typeFace);
switch (alignment){
case ALIGNCENTER:

View File

@ -83,6 +83,9 @@ public class Cocos2dxActivity extends Activity{
backgroundMusicPlayer = new Cocos2dxMusic(this);
soundPlayer = new Cocos2dxSound(this);
// init bitmap context
Cocos2dxBitmap.setContext(this);
handler = new Handler(){
public void handleMessage(Message msg){
switch(msg.what){

View File

@ -4,6 +4,7 @@ import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.LinkedList;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
@ -11,6 +12,7 @@ import android.graphics.Paint;
import android.graphics.Typeface;
import android.graphics.Paint.Align;
import android.graphics.Paint.FontMetricsInt;
import android.util.Log;
public class Cocos2dxBitmap{
/*
@ -21,6 +23,12 @@ public class Cocos2dxBitmap{
private static final int ALIGNLEFT = 0x31;
private static final int ALIGNRIGHT = 0x32;
private static Context context;
public static void setContext(Context context){
Cocos2dxBitmap.context = context;
}
/*
* @width: the width to draw, it can be 0
* @height: the height to draw, it can be 0
@ -119,7 +127,6 @@ public class Cocos2dxBitmap{
}
}
return new TextProperty(maxContentWidth, h, lines);
}
@ -244,8 +251,28 @@ public class Cocos2dxBitmap{
Paint paint = new Paint();
paint.setColor(Color.WHITE);
paint.setTextSize(fontSize);
paint.setTypeface(Typeface.create(fontName, Typeface.NORMAL));
paint.setAntiAlias(true);
Typeface typeFace = null;
/*
* Set type face for paint, now it support .ttf file.
*/
if (fontName.endsWith(".ttf")){
try {
typeFace = Typeface.createFromAsset(context.getAssets(), fontName);
} catch (Exception e){
Log.e("Cocos2dxBitmap",
"error to create ttf type face: " + fontName);
/*
* The file may not find, use system font
*/
paint.setTypeface(Typeface.create(fontName, Typeface.NORMAL));
}
}
else {
paint.setTypeface(Typeface.create(fontName, Typeface.NORMAL));
}
switch (alignment){
case ALIGNCENTER:

View File

@ -1 +1 @@
5abcfaee751bdf7cdcd592e7e566090b500eddca
a94593d95f2fff9766c611a35a6593a87d490698

View File

@ -21,11 +21,18 @@ 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.
****************************************************************************/
#include <Foundation/Foundation.h>
#include <UIKit/UIKit.h>
#include "CCImage.h"
#include "CCFileUtils.h"
#include <string>
#import "CCImage.h"
#import "CCFileUtils.h"
#import <string>
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#if CC_FONT_LABEL_SUPPORT
// FontLabel support
#import "FontLabel/FontManager.h"
#import "FontLabel/FontLabelStringDrawing.h"
#endif// CC_FONT_LABEL_SUPPORT
typedef struct
{
@ -335,104 +342,97 @@ static bool _initWithString(const char * pText, cocos2d::CCImage::ETextAlign eAl
{
CC_BREAK_IF(! pText || ! pInfo);
NSString * string = [NSString stringWithUTF8String:pText];
NSString * str = [NSString stringWithUTF8String:pText];
NSString * fntName = [NSString stringWithUTF8String:pFontName];
CGSize dim;
// create the font
NSString * fntName = _isValidFontName(pFontName) ? [NSString stringWithUTF8String:pFontName] : @"MarkerFelt-Wide";
UIFont * font = [UIFont fontWithName:fntName size:nSize];
id font;
font = [UIFont fontWithName:fntName size:nSize];
if (font)
{
dim = [str sizeWithFont:font];
}
#if CC_FONT_LABEL_SUPPORT
if (! font)
{
font = [[FontManager sharedManager] zFontWithName:fntName pointSize:nSize];
if (font)
{
//dim = [str sizeWithZFont:font];
dim = [FontLabelStringDrawingHelper sizeWithZFont:str zfont:font];
}
}
#endif // CC_FONT_LABEL_SUPPORT
if (! font)
{
fntName = _isValidFontName(pFontName) ? fntName : @"MarkerFelt-Wide";
font = [UIFont fontWithName:fntName size:nSize];
if (! font)
{
font = [UIFont systemFontOfSize:nSize];
}
if (font)
{
dim = [str sizeWithFont:font];
}
}
if (pInfo->width != 0 || pInfo->height != 0)
{
dim.width = pInfo->width;
dim.height = pInfo->height;
}
else
{
pInfo->width = dim.width;
pInfo->height = dim.height;
}
CC_BREAK_IF(! font);
// measure text size with specified font and determine the rectangle to draw text in
unsigned uHoriFlag = eAlign & 0x0f;
unsigned uVertFlag = (eAlign & 0xf0) >> 4;
CGSize textSize; // the size which total text layout need
CGSize canvasSize; // the size which malloc for drawtext
CGRect textRect; // the rectangle which draw text in
if (0 >= pInfo->width)
{
// the content width no limit, use 0x7fffffff as a big enough bounds
CGSize contentSize = CGSizeMake(0x7fffffff, 0x7fffffff);
textSize = [string sizeWithFont:font constrainedToSize:contentSize];
canvasSize = textSize;
pInfo->width = (size_t) textSize.width;
pInfo->height = (size_t) textSize.height;
textRect = CGRectMake(0, 0, textSize.width, textSize.height);
}
else
{
// the content height no limit, use 0x7fffffff as a big enough height
CGSize contentSize = CGSizeMake(pInfo->width, 0x7fffffff);
textSize = [string sizeWithFont:font constrainedToSize:contentSize];
if (0 >= pInfo->height)
{
canvasSize = textSize;
pInfo->height = (size_t) textSize.height;
pInfo->width = (size_t) textSize.width;
textRect = CGRectMake(0, 0, textSize.width, textSize.height);
}
else
{
canvasSize.width = pInfo->width;
if (textSize.height <= pInfo->height)
{
canvasSize.height = pInfo->height;
}
else
{
// the height of text larger than the specified
// let canvas's height larger a little,
// make sure the last line of text in specified rectangle draw in canvas
size_t lineHeight = (size_t)font.lineHeight;
canvasSize.height = ((pInfo->height + lineHeight - 1) / lineHeight) * lineHeight;
}
canvasSize = CGSizeMake(pInfo->width, MAX(pInfo->height, (size_t)textSize.height));
textRect.size = textSize;
textRect.origin.x = (1 == uHoriFlag) ? 0 // align to left
: (2 == uHoriFlag) ? pInfo->width - textSize.width // align to right
: (pInfo->width - textSize.width) / 2; // align to center
textRect.origin.y = (1 == uVertFlag || textSize.height >= pInfo->height) ? 0 // align to top
: (2 == uVertFlag) ? pInfo->height - textSize.height // align to bottom
: (pInfo->height - textSize.height) / 2; // align to center
}
}
// malloc space to store pixels data
size_t width = (size_t)canvasSize.width;
size_t height = (size_t)canvasSize.height;
unsigned char* data = new unsigned char[width * height * 4];
CC_BREAK_IF(! data);
memset(data, 0, height * width * 4);
unsigned char* data = new unsigned char[pInfo->width * pInfo->height * 4];
memset(data, 0, pInfo->width * pInfo->height * 4);
// draw text
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(data, width, height, 8, width * 4, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGContextRef context = CGBitmapContextCreate(data, dim.width, dim.height, 8, dim.width * 4, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGColorSpaceRelease(colorSpace);
CC_BREAK_IF(!context);
if (! context)
{
delete[] data;
break;
}
CGContextSetRGBFillColor(context, 1, 1, 1, 1);
CGContextTranslateCTM(context, 0.0f, height);
CGContextTranslateCTM(context, 0.0f, dim.height);
CGContextScaleCTM(context, 1.0f, -1.0f); //NOTE: NSString draws in UIKit referential i.e. renders upside-down compared to CGBitmapContext referential
UIGraphicsPushContext(context);
// measure text size with specified font and determine the rectangle to draw text in
unsigned uHoriFlag = eAlign & 0x0f;
UITextAlignment align = (2 == uHoriFlag) ? UITextAlignmentRight
: (3 == uHoriFlag) ? UITextAlignmentCenter
: UITextAlignmentLeft;
[string drawInRect:textRect withFont:font lineBreakMode:UILineBreakModeWordWrap alignment:align];
// normal fonts
if( [font isKindOfClass:[UIFont class] ] )
{
[str drawInRect:CGRectMake(0, 0, dim.width, dim.height) withFont:font lineBreakMode:UILineBreakModeWordWrap alignment:align];
}
#if CC_FONT_LABEL_SUPPORT
else // ZFont class
{
//[str drawInRect:CGRectMake(0, 0, dim.width, dim.height) withZFont:font lineBreakMode:UILineBreakModeWordWrap alignment:align];
[FontLabelStringDrawingHelper drawInRect:str rect:CGRectMake(0, 0, dim.width, dim.height) withZFont:font lineBreakMode:UILineBreakModeWordWrap alignment:align];
}
#endif
UIGraphicsPopContext();
@ -473,7 +473,12 @@ bool CCImage::initWithImageFile(const char * strPath, EImageFormat eImgFmt/* = e
return initWithImageData(data.getBuffer(), data.getSize(), eImgFmt);
}
bool CCImage::initWithImageData(void * pData, int nDataLen, EImageFormat eFmt/* = eSrcFmtPng*/)
bool CCImage::initWithImageData(void * pData,
int nDataLen,
EImageFormat eFmt,
int nWidth,
int nHeight,
int nBitsPerComponent)
{
bool bRet = false;
tImageInfo info = {0};
@ -520,5 +525,10 @@ bool CCImage::initWithString(
return true;
}
bool CCImage::saveToFile(const char *pszFilePath, bool bIsToRGB)
{
return false;
}
NS_CC_END;

View File

@ -0,0 +1,44 @@
//
// FontLabel.h
// FontLabel
//
// Created by Kevin Ballard on 5/8/09.
// Copyright © 2009 Zynga Game Networks
//
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@class ZFont;
@class ZAttributedString;
@interface FontLabel : UILabel {
void *reserved; // works around a bug in UILabel
ZFont *zFont;
ZAttributedString *zAttributedText;
}
@property (nonatomic, setter=setCGFont:) CGFontRef cgFont __AVAILABILITY_INTERNAL_DEPRECATED;
@property (nonatomic, assign) CGFloat pointSize __AVAILABILITY_INTERNAL_DEPRECATED;
@property (nonatomic, retain, setter=setZFont:) ZFont *zFont;
// if attributedText is nil, fall back on using the inherited UILabel properties
// if attributedText is non-nil, the font/text/textColor
// in addition, adjustsFontSizeToFitWidth does not work with attributed text
@property (nonatomic, copy) ZAttributedString *zAttributedText;
// -initWithFrame:fontName:pointSize: uses FontManager to look up the font name
- (id)initWithFrame:(CGRect)frame fontName:(NSString *)fontName pointSize:(CGFloat)pointSize;
- (id)initWithFrame:(CGRect)frame zFont:(ZFont *)font;
- (id)initWithFrame:(CGRect)frame font:(CGFontRef)font pointSize:(CGFloat)pointSize __AVAILABILITY_INTERNAL_DEPRECATED;
@end

View File

@ -0,0 +1,195 @@
//
// FontLabel.m
// FontLabel
//
// Created by Kevin Ballard on 5/8/09.
// Copyright © 2009 Zynga Game Networks
//
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#import "FontLabel.h"
#import "FontManager.h"
#import "FontLabelStringDrawing.h"
#import "ZFont.h"
@interface ZFont (ZFontPrivate)
@property (nonatomic, readonly) CGFloat ratio;
@end
@implementation FontLabel
@synthesize zFont;
@synthesize zAttributedText;
- (id)initWithFrame:(CGRect)frame fontName:(NSString *)fontName pointSize:(CGFloat)pointSize {
return [self initWithFrame:frame zFont:[[FontManager sharedManager] zFontWithName:fontName pointSize:pointSize]];
}
- (id)initWithFrame:(CGRect)frame zFont:(ZFont *)font {
if ((self = [super initWithFrame:frame])) {
zFont = [font retain];
}
return self;
}
- (id)initWithFrame:(CGRect)frame font:(CGFontRef)font pointSize:(CGFloat)pointSize {
return [self initWithFrame:frame zFont:[ZFont fontWithCGFont:font size:pointSize]];
}
- (CGFontRef)cgFont {
return self.zFont.cgFont;
}
- (void)setCGFont:(CGFontRef)font {
if (self.zFont.cgFont != font) {
self.zFont = [ZFont fontWithCGFont:font size:self.zFont.pointSize];
}
}
- (CGFloat)pointSize {
return self.zFont.pointSize;
}
- (void)setPointSize:(CGFloat)pointSize {
if (self.zFont.pointSize != pointSize) {
self.zFont = [ZFont fontWithCGFont:self.zFont.cgFont size:pointSize];
}
}
- (void)setZAttributedText:(ZAttributedString *)attStr {
if (zAttributedText != attStr) {
[zAttributedText release];
zAttributedText = [attStr copy];
[self setNeedsDisplay];
}
}
- (void)drawTextInRect:(CGRect)rect {
if (self.zFont == NULL && self.zAttributedText == nil) {
[super drawTextInRect:rect];
return;
}
if (self.zAttributedText == nil) {
// this method is documented as setting the text color for us, but that doesn't appear to be the case
if (self.highlighted) {
[(self.highlightedTextColor ?: [UIColor whiteColor]) setFill];
} else {
[(self.textColor ?: [UIColor blackColor]) setFill];
}
ZFont *actualFont = self.zFont;
CGSize origSize = rect.size;
if (self.numberOfLines == 1) {
origSize.height = actualFont.leading;
CGPoint point = CGPointMake(rect.origin.x,
rect.origin.y + roundf(((rect.size.height - actualFont.leading) / 2.0f)));
CGSize size = [self.text sizeWithZFont:actualFont];
if (self.adjustsFontSizeToFitWidth && self.minimumFontSize < actualFont.pointSize) {
if (size.width > origSize.width) {
CGFloat desiredRatio = (origSize.width * actualFont.ratio) / size.width;
CGFloat desiredPointSize = desiredRatio * actualFont.pointSize / actualFont.ratio;
actualFont = [actualFont fontWithSize:MAX(MAX(desiredPointSize, self.minimumFontSize), 1.0f)];
size = [self.text sizeWithZFont:actualFont];
}
if (!CGSizeEqualToSize(origSize, size)) {
switch (self.baselineAdjustment) {
case UIBaselineAdjustmentAlignCenters:
point.y += roundf((origSize.height - size.height) / 2.0f);
break;
case UIBaselineAdjustmentAlignBaselines:
point.y += (self.zFont.ascender - actualFont.ascender);
break;
case UIBaselineAdjustmentNone:
break;
}
}
}
size.width = MIN(size.width, origSize.width);
// adjust the point for alignment
switch (self.textAlignment) {
case UITextAlignmentLeft:
break;
case UITextAlignmentCenter:
point.x += (origSize.width - size.width) / 2.0f;
break;
case UITextAlignmentRight:
point.x += origSize.width - size.width;
break;
}
[self.text drawAtPoint:point forWidth:size.width withZFont:actualFont lineBreakMode:self.lineBreakMode];
} else {
CGSize size = [self.text sizeWithZFont:actualFont constrainedToSize:origSize lineBreakMode:self.lineBreakMode numberOfLines:self.numberOfLines];
CGPoint point = rect.origin;
point.y += roundf((rect.size.height - size.height) / 2.0f);
rect = (CGRect){point, CGSizeMake(rect.size.width, size.height)};
[self.text drawInRect:rect withZFont:actualFont lineBreakMode:self.lineBreakMode alignment:self.textAlignment numberOfLines:self.numberOfLines];
}
} else {
ZAttributedString *attStr = self.zAttributedText;
if (self.highlighted) {
// modify the string to change the base color
ZMutableAttributedString *mutStr = [[attStr mutableCopy] autorelease];
NSRange activeRange = NSMakeRange(0, attStr.length);
while (activeRange.length > 0) {
NSRange effective;
UIColor *color = [attStr attribute:ZForegroundColorAttributeName atIndex:activeRange.location
longestEffectiveRange:&effective inRange:activeRange];
if (color == nil) {
[mutStr addAttribute:ZForegroundColorAttributeName value:[UIColor whiteColor] range:effective];
}
activeRange.location += effective.length, activeRange.length -= effective.length;
}
attStr = mutStr;
}
CGSize size = [attStr sizeConstrainedToSize:rect.size lineBreakMode:self.lineBreakMode numberOfLines:self.numberOfLines];
CGPoint point = rect.origin;
point.y += roundf((rect.size.height - size.height) / 2.0f);
rect = (CGRect){point, CGSizeMake(rect.size.width, size.height)};
[attStr drawInRect:rect withLineBreakMode:self.lineBreakMode alignment:self.textAlignment numberOfLines:self.numberOfLines];
}
}
- (CGRect)textRectForBounds:(CGRect)bounds limitedToNumberOfLines:(NSInteger)numberOfLines {
if (self.zFont == NULL && self.zAttributedText == nil) {
return [super textRectForBounds:bounds limitedToNumberOfLines:numberOfLines];
}
if (numberOfLines == 1) {
// if numberOfLines == 1 we need to use the version that converts spaces
CGSize size;
if (self.zAttributedText == nil) {
size = [self.text sizeWithZFont:self.zFont];
} else {
size = [self.zAttributedText size];
}
bounds.size.width = MIN(bounds.size.width, size.width);
bounds.size.height = MIN(bounds.size.height, size.height);
} else {
if (numberOfLines > 0) bounds.size.height = MIN(bounds.size.height, self.zFont.leading * numberOfLines);
if (self.zAttributedText == nil) {
bounds.size = [self.text sizeWithZFont:self.zFont constrainedToSize:bounds.size lineBreakMode:self.lineBreakMode];
} else {
bounds.size = [self.zAttributedText sizeConstrainedToSize:bounds.size lineBreakMode:self.lineBreakMode];
}
}
return bounds;
}
- (void)dealloc {
[zFont release];
[zAttributedText release];
[super dealloc];
}
@end

View File

@ -0,0 +1,81 @@
//
// FontLabelStringDrawing.h
// FontLabel
//
// Created by Kevin Ballard on 5/5/09.
// Copyright © 2009 Zynga Game Networks
// Copyright (c) 2011 cocos2d-x.org
//
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#import <UIKit/UIKit.h>
#import "ZAttributedString.h"
@class ZFont;
@interface NSString (FontLabelStringDrawing)
// CGFontRef-based methods
- (CGSize)sizeWithCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize __AVAILABILITY_INTERNAL_DEPRECATED;
- (CGSize)sizeWithCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize constrainedToSize:(CGSize)size __AVAILABILITY_INTERNAL_DEPRECATED;
- (CGSize)sizeWithCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize constrainedToSize:(CGSize)size
lineBreakMode:(UILineBreakMode)lineBreakMode __AVAILABILITY_INTERNAL_DEPRECATED;
- (CGSize)drawAtPoint:(CGPoint)point withCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize __AVAILABILITY_INTERNAL_DEPRECATED;
- (CGSize)drawInRect:(CGRect)rect withCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize __AVAILABILITY_INTERNAL_DEPRECATED;
- (CGSize)drawInRect:(CGRect)rect withCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize
lineBreakMode:(UILineBreakMode)lineBreakMode __AVAILABILITY_INTERNAL_DEPRECATED;
- (CGSize)drawInRect:(CGRect)rect withCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize
lineBreakMode:(UILineBreakMode)lineBreakMode alignment:(UITextAlignment)alignment __AVAILABILITY_INTERNAL_DEPRECATED;
// ZFont-based methods
- (CGSize)sizeWithZFont:(ZFont *)font;
- (CGSize)sizeWithZFont:(ZFont *)font constrainedToSize:(CGSize)size;
- (CGSize)sizeWithZFont:(ZFont *)font constrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode;
- (CGSize)sizeWithZFont:(ZFont *)font constrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode
numberOfLines:(NSUInteger)numberOfLines;
- (CGSize)drawAtPoint:(CGPoint)point withZFont:(ZFont *)font;
- (CGSize)drawAtPoint:(CGPoint)point forWidth:(CGFloat)width withZFont:(ZFont *)font lineBreakMode:(UILineBreakMode)lineBreakMode;
- (CGSize)drawInRect:(CGRect)rect withZFont:(ZFont *)font;
- (CGSize)drawInRect:(CGRect)rect withZFont:(ZFont *)font lineBreakMode:(UILineBreakMode)lineBreakMode;
- (CGSize)drawInRect:(CGRect)rect withZFont:(ZFont *)font lineBreakMode:(UILineBreakMode)lineBreakMode
alignment:(UITextAlignment)alignment;
- (CGSize)drawInRect:(CGRect)rect withZFont:(ZFont *)font lineBreakMode:(UILineBreakMode)lineBreakMode
alignment:(UITextAlignment)alignment numberOfLines:(NSUInteger)numberOfLines;
@end
@interface ZAttributedString (ZAttributedStringDrawing)
- (CGSize)size;
- (CGSize)sizeConstrainedToSize:(CGSize)size;
- (CGSize)sizeConstrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode;
- (CGSize)sizeConstrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode
numberOfLines:(NSUInteger)numberOfLines;
- (CGSize)drawAtPoint:(CGPoint)point;
- (CGSize)drawAtPoint:(CGPoint)point forWidth:(CGFloat)width lineBreakMode:(UILineBreakMode)lineBreakMode;
- (CGSize)drawInRect:(CGRect)rect;
- (CGSize)drawInRect:(CGRect)rect withLineBreakMode:(UILineBreakMode)lineBreakMode;
- (CGSize)drawInRect:(CGRect)rect withLineBreakMode:(UILineBreakMode)lineBreakMode alignment:(UITextAlignment)alignment;
- (CGSize)drawInRect:(CGRect)rect withLineBreakMode:(UILineBreakMode)lineBreakMode alignment:(UITextAlignment)alignment
numberOfLines:(NSUInteger)numberOfLines;
@end
// This class is used to invoke in .mm file.
// Can not invoke FontLabelStringDrawing directly in .mm.
// It seems that, in .mm it can not support category.
@interface FontLabelStringDrawingHelper : NSObject {
}
+ (CGSize)sizeWithZFont:(NSString*)string zfont:(ZFont *)font;
+ (CGSize)drawInRect:(NSString*)string rect:(CGRect)rect withZFont:(ZFont *)font
lineBreakMode:(UILineBreakMode)lineBreakMode
alignment:(UITextAlignment)alignment;
@end

View File

@ -0,0 +1,907 @@
//
// FontLabelStringDrawing.m
// FontLabel
//
// Created by Kevin Ballard on 5/5/09.
// Copyright © 2009 Zynga Game Networks
// Copyright (c) 2011 cocos2d-x.org
//
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#import "FontLabelStringDrawing.h"
#import "ZFont.h"
#import "ZAttributedStringPrivate.h"
@interface ZFont (ZFontPrivate)
@property (nonatomic, readonly) CGFloat ratio;
@end
#define kUnicodeHighSurrogateStart 0xD800
#define kUnicodeHighSurrogateEnd 0xDBFF
#define kUnicodeHighSurrogateMask kUnicodeHighSurrogateStart
#define kUnicodeLowSurrogateStart 0xDC00
#define kUnicodeLowSurrogateEnd 0xDFFF
#define kUnicodeLowSurrogateMask kUnicodeLowSurrogateStart
#define kUnicodeSurrogateTypeMask 0xFC00
#define UnicharIsHighSurrogate(c) ((c & kUnicodeSurrogateTypeMask) == kUnicodeHighSurrogateMask)
#define UnicharIsLowSurrogate(c) ((c & kUnicodeSurrogateTypeMask) == kUnicodeLowSurrogateMask)
#define ConvertSurrogatePairToUTF32(high, low) ((UInt32)((high - 0xD800) * 0x400 + (low - 0xDC00) + 0x10000))
typedef enum {
kFontTableFormat4 = 4,
kFontTableFormat12 = 12,
} FontTableFormat;
typedef struct fontTable {
NSUInteger retainCount;
CFDataRef cmapTable;
FontTableFormat format;
union {
struct {
UInt16 segCountX2;
UInt16 *endCodes;
UInt16 *startCodes;
UInt16 *idDeltas;
UInt16 *idRangeOffsets;
} format4;
struct {
UInt32 nGroups;
struct {
UInt32 startCharCode;
UInt32 endCharCode;
UInt32 startGlyphCode;
} *groups;
} format12;
} cmap;
} fontTable;
static FontTableFormat supportedFormats[] = { kFontTableFormat4, kFontTableFormat12 };
static size_t supportedFormatsCount = sizeof(supportedFormats) / sizeof(FontTableFormat);
static fontTable *newFontTable(CFDataRef cmapTable, FontTableFormat format) {
fontTable *table = (struct fontTable *)malloc(sizeof(struct fontTable));
table->retainCount = 1;
table->cmapTable = CFRetain(cmapTable);
table->format = format;
return table;
}
static fontTable *retainFontTable(fontTable *table) {
if (table != NULL) {
table->retainCount++;
}
return table;
}
static void releaseFontTable(fontTable *table) {
if (table != NULL) {
if (table->retainCount <= 1) {
CFRelease(table->cmapTable);
free(table);
} else {
table->retainCount--;
}
}
}
static const void *fontTableRetainCallback(CFAllocatorRef allocator, const void *value) {
return retainFontTable((fontTable *)value);
}
static void fontTableReleaseCallback(CFAllocatorRef allocator, const void *value) {
releaseFontTable((fontTable *)value);
}
static const CFDictionaryValueCallBacks kFontTableDictionaryValueCallBacks = {
.version = 0,
.retain = &fontTableRetainCallback,
.release = &fontTableReleaseCallback,
.copyDescription = NULL,
.equal = NULL
};
// read the cmap table from the font
// we only know how to understand some of the table formats at the moment
static fontTable *readFontTableFromCGFont(CGFontRef font) {
CFDataRef cmapTable = CGFontCopyTableForTag(font, 'cmap');
NSCAssert1(cmapTable != NULL, @"CGFontCopyTableForTag returned NULL for 'cmap' tag in font %@",
(font ? [(id)CFCopyDescription(font) autorelease] : @"(null)"));
const UInt8 * const bytes = CFDataGetBytePtr(cmapTable);
NSCAssert1(OSReadBigInt16(bytes, 0) == 0, @"cmap table for font %@ has bad version number",
(font ? [(id)CFCopyDescription(font) autorelease] : @"(null)"));
UInt16 numberOfSubtables = OSReadBigInt16(bytes, 2);
const UInt8 *unicodeSubtable = NULL;
//UInt16 unicodeSubtablePlatformID;
UInt16 unicodeSubtablePlatformSpecificID;
FontTableFormat unicodeSubtableFormat;
const UInt8 * const encodingSubtables = &bytes[4];
for (UInt16 i = 0; i < numberOfSubtables; i++) {
const UInt8 * const encodingSubtable = &encodingSubtables[8 * i];
UInt16 platformID = OSReadBigInt16(encodingSubtable, 0);
UInt16 platformSpecificID = OSReadBigInt16(encodingSubtable, 2);
// find the best subtable
// best is defined by a combination of encoding and format
// At the moment we only support format 4, so ignore all other format tables
// We prefer platformID == 0, but we will also accept Microsoft's unicode format
if (platformID == 0 || (platformID == 3 && platformSpecificID == 1)) {
BOOL preferred = NO;
if (unicodeSubtable == NULL) {
preferred = YES;
} else if (platformID == 0 && platformSpecificID > unicodeSubtablePlatformSpecificID) {
preferred = YES;
}
if (preferred) {
UInt32 offset = OSReadBigInt32(encodingSubtable, 4);
const UInt8 *subtable = &bytes[offset];
UInt16 format = OSReadBigInt16(subtable, 0);
for (size_t i = 0; i < supportedFormatsCount; i++) {
if (format == supportedFormats[i]) {
if (format >= 8) {
// the version is a fixed-point
UInt16 formatFrac = OSReadBigInt16(subtable, 2);
if (formatFrac != 0) {
// all the current formats with a Fixed version are always *.0
continue;
}
}
unicodeSubtable = subtable;
//unicodeSubtablePlatformID = platformID;
unicodeSubtablePlatformSpecificID = platformSpecificID;
unicodeSubtableFormat = format;
break;
}
}
}
}
}
fontTable *table = NULL;
if (unicodeSubtable != NULL) {
table = newFontTable(cmapTable, unicodeSubtableFormat);
switch (unicodeSubtableFormat) {
case kFontTableFormat4:
// subtable format 4
//UInt16 length = OSReadBigInt16(unicodeSubtable, 2);
//UInt16 language = OSReadBigInt16(unicodeSubtable, 4);
table->cmap.format4.segCountX2 = OSReadBigInt16(unicodeSubtable, 6);
//UInt16 searchRange = OSReadBigInt16(unicodeSubtable, 8);
//UInt16 entrySelector = OSReadBigInt16(unicodeSubtable, 10);
//UInt16 rangeShift = OSReadBigInt16(unicodeSubtable, 12);
table->cmap.format4.endCodes = (UInt16*)&unicodeSubtable[14];
table->cmap.format4.startCodes = (UInt16*)&((UInt8*)table->cmap.format4.endCodes)[table->cmap.format4.segCountX2+2];
table->cmap.format4.idDeltas = (UInt16*)&((UInt8*)table->cmap.format4.startCodes)[table->cmap.format4.segCountX2];
table->cmap.format4.idRangeOffsets = (UInt16*)&((UInt8*)table->cmap.format4.idDeltas)[table->cmap.format4.segCountX2];
//UInt16 *glyphIndexArray = &idRangeOffsets[segCountX2];
break;
case kFontTableFormat12:
table->cmap.format12.nGroups = OSReadBigInt32(unicodeSubtable, 12);
table->cmap.format12.groups = (void *)&unicodeSubtable[16];
break;
default:
releaseFontTable(table);
table = NULL;
}
}
CFRelease(cmapTable);
return table;
}
// outGlyphs must be at least size n
static void mapCharactersToGlyphsInFont(const fontTable *table, unichar characters[], size_t charLen, CGGlyph outGlyphs[], size_t *outGlyphLen) {
if (table != NULL) {
NSUInteger j = 0;
switch (table->format) {
case kFontTableFormat4: {
for (NSUInteger i = 0; i < charLen; i++, j++) {
unichar c = characters[i];
UInt16 segOffset;
BOOL foundSegment = NO;
for (segOffset = 0; segOffset < table->cmap.format4.segCountX2; segOffset += 2) {
UInt16 endCode = OSReadBigInt16(table->cmap.format4.endCodes, segOffset);
if (endCode >= c) {
foundSegment = YES;
break;
}
}
if (!foundSegment) {
// no segment
// this is an invalid font
outGlyphs[j] = 0;
} else {
UInt16 startCode = OSReadBigInt16(table->cmap.format4.startCodes, segOffset);
if (!(startCode <= c)) {
// the code falls in a hole between segments
outGlyphs[j] = 0;
} else {
UInt16 idRangeOffset = OSReadBigInt16(table->cmap.format4.idRangeOffsets, segOffset);
if (idRangeOffset == 0) {
UInt16 idDelta = OSReadBigInt16(table->cmap.format4.idDeltas, segOffset);
outGlyphs[j] = (c + idDelta) % 65536;
} else {
// use the glyphIndexArray
UInt16 glyphOffset = idRangeOffset + 2 * (c - startCode);
outGlyphs[j] = OSReadBigInt16(&((UInt8*)table->cmap.format4.idRangeOffsets)[segOffset], glyphOffset);
}
}
}
}
break;
}
case kFontTableFormat12: {
UInt32 lastSegment = UINT32_MAX;
for (NSUInteger i = 0; i < charLen; i++, j++) {
unichar c = characters[i];
UInt32 c32 = c;
if (UnicharIsHighSurrogate(c)) {
if (i+1 < charLen) { // do we have another character after this one?
unichar cc = characters[i+1];
if (UnicharIsLowSurrogate(cc)) {
c32 = ConvertSurrogatePairToUTF32(c, cc);
i++;
}
}
}
// Start the heuristic search
// If this is an ASCII char, just do a linear search
// Otherwise do a hinted, modified binary search
// Start the first pivot at the last range found
// And when moving the pivot, limit the movement by increasing
// powers of two. This should help with locality
__typeof__(table->cmap.format12.groups[0]) *foundGroup = NULL;
if (c32 <= 0x7F) {
// ASCII
for (UInt32 idx = 0; idx < table->cmap.format12.nGroups; idx++) {
__typeof__(table->cmap.format12.groups[idx]) *group = &table->cmap.format12.groups[idx];
if (c32 < OSSwapBigToHostInt32(group->startCharCode)) {
// we've fallen into a hole
break;
} else if (c32 <= OSSwapBigToHostInt32(group->endCharCode)) {
// this is the range
foundGroup = group;
break;
}
}
} else {
// heuristic search
UInt32 maxJump = (lastSegment == UINT32_MAX ? UINT32_MAX / 2 : 8);
UInt32 lowIdx = 0, highIdx = table->cmap.format12.nGroups; // highIdx is the first invalid idx
UInt32 pivot = (lastSegment == UINT32_MAX ? lowIdx + (highIdx - lowIdx) / 2 : lastSegment);
while (highIdx > lowIdx) {
__typeof__(table->cmap.format12.groups[pivot]) *group = &table->cmap.format12.groups[pivot];
if (c32 < OSSwapBigToHostInt32(group->startCharCode)) {
highIdx = pivot;
} else if (c32 > OSSwapBigToHostInt32(group->endCharCode)) {
lowIdx = pivot + 1;
} else {
// we've hit the range
foundGroup = group;
break;
}
if (highIdx - lowIdx > maxJump * 2) {
if (highIdx == pivot) {
pivot -= maxJump;
} else {
pivot += maxJump;
}
maxJump *= 2;
} else {
pivot = lowIdx + (highIdx - lowIdx) / 2;
}
}
if (foundGroup != NULL) lastSegment = pivot;
}
if (foundGroup == NULL) {
outGlyphs[j] = 0;
} else {
outGlyphs[j] = (CGGlyph)(OSSwapBigToHostInt32(foundGroup->startGlyphCode) +
(c32 - OSSwapBigToHostInt32(foundGroup->startCharCode)));
}
}
break;
}
}
if (outGlyphLen != NULL) *outGlyphLen = j;
} else {
// we have no table, so just null out the glyphs
bzero(outGlyphs, charLen*sizeof(CGGlyph));
if (outGlyphLen != NULL) *outGlyphLen = 0;
}
}
static BOOL mapGlyphsToAdvancesInFont(ZFont *font, size_t n, CGGlyph glyphs[], CGFloat outAdvances[]) {
int advances[n];
if (CGFontGetGlyphAdvances(font.cgFont, glyphs, n, advances)) {
CGFloat ratio = font.ratio;
for (size_t i = 0; i < n; i++) {
outAdvances[i] = advances[i]*ratio;
}
return YES;
} else {
bzero(outAdvances, n*sizeof(CGFloat));
}
return NO;
}
static id getValueOrDefaultForRun(ZAttributeRun *run, NSString *key) {
id value = [run.attributes objectForKey:key];
if (value == nil) {
static NSDictionary *defaultValues = nil;
if (defaultValues == nil) {
defaultValues = [[NSDictionary alloc] initWithObjectsAndKeys:
[ZFont fontWithUIFont:[UIFont systemFontOfSize:12]], ZFontAttributeName,
[UIColor blackColor], ZForegroundColorAttributeName,
[UIColor clearColor], ZBackgroundColorAttributeName,
[NSNumber numberWithInt:ZUnderlineStyleNone], ZUnderlineStyleAttributeName,
nil];
}
value = [defaultValues objectForKey:key];
}
return value;
}
static void readRunInformation(NSArray *attributes, NSUInteger len, CFMutableDictionaryRef fontTableMap,
NSUInteger index, ZAttributeRun **currentRun, NSUInteger *nextRunStart,
ZFont **currentFont, fontTable **currentTable) {
*currentRun = [attributes objectAtIndex:index];
*nextRunStart = ([attributes count] > index+1 ? [[attributes objectAtIndex:index+1] index] : len);
*currentFont = getValueOrDefaultForRun(*currentRun, ZFontAttributeName);
if (!CFDictionaryGetValueIfPresent(fontTableMap, (*currentFont).cgFont, (const void **)currentTable)) {
*currentTable = readFontTableFromCGFont((*currentFont).cgFont);
CFDictionarySetValue(fontTableMap, (*currentFont).cgFont, *currentTable);
releaseFontTable(*currentTable);
}
}
static CGSize drawOrSizeTextConstrainedToSize(BOOL performDraw, NSString *string, NSArray *attributes, CGSize constrainedSize, NSUInteger maxLines,
UILineBreakMode lineBreakMode, UITextAlignment alignment, BOOL ignoreColor) {
NSUInteger len = [string length];
NSUInteger idx = 0;
CGPoint drawPoint = CGPointZero;
CGSize retValue = CGSizeZero;
CGContextRef ctx = (performDraw ? UIGraphicsGetCurrentContext() : NULL);
BOOL convertNewlines = (maxLines == 1);
// Extract the characters from the string
// Convert newlines to spaces if necessary
unichar *characters = (unichar *)malloc(sizeof(unichar) * len);
if (convertNewlines) {
NSCharacterSet *charset = [NSCharacterSet newlineCharacterSet];
NSRange range = NSMakeRange(0, len);
size_t cIdx = 0;
while (range.length > 0) {
NSRange newlineRange = [string rangeOfCharacterFromSet:charset options:0 range:range];
if (newlineRange.location == NSNotFound) {
[string getCharacters:&characters[cIdx] range:range];
cIdx += range.length;
break;
} else {
NSUInteger delta = newlineRange.location - range.location;
if (newlineRange.location > range.location) {
[string getCharacters:&characters[cIdx] range:NSMakeRange(range.location, delta)];
}
cIdx += delta;
characters[cIdx] = (unichar)' ';
cIdx++;
delta += newlineRange.length;
range.location += delta, range.length -= delta;
if (newlineRange.length == 1 && range.length >= 1 &&
[string characterAtIndex:newlineRange.location] == (unichar)'\r' &&
[string characterAtIndex:range.location] == (unichar)'\n') {
// CRLF sequence, skip the LF
range.location += 1, range.length -= 1;
}
}
}
len = cIdx;
} else {
[string getCharacters:characters range:NSMakeRange(0, len)];
}
// Create storage for glyphs and advances
CGGlyph *glyphs;
CGFloat *advances;
{
NSUInteger maxRunLength = 0;
ZAttributeRun *a = [attributes objectAtIndex:0];
for (NSUInteger i = 1; i < [attributes count]; i++) {
ZAttributeRun *b = [attributes objectAtIndex:i];
maxRunLength = MAX(maxRunLength, b.index - a.index);
a = b;
}
maxRunLength = MAX(maxRunLength, len - a.index);
maxRunLength++; // for a potential ellipsis
glyphs = (CGGlyph *)malloc(sizeof(CGGlyph) * maxRunLength);
advances = (CGFloat *)malloc(sizeof(CGFloat) * maxRunLength);
}
// Use this table to cache all fontTable objects
CFMutableDictionaryRef fontTableMap = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks,
&kFontTableDictionaryValueCallBacks);
// Fetch initial style values
NSUInteger currentRunIdx = 0;
ZAttributeRun *currentRun;
NSUInteger nextRunStart;
ZFont *currentFont;
fontTable *currentTable;
#define READ_RUN() readRunInformation(attributes, len, fontTableMap, \
currentRunIdx, &currentRun, &nextRunStart, \
&currentFont, &currentTable)
READ_RUN();
// fetch the glyphs for the first run
size_t glyphCount;
NSUInteger glyphIdx;
#define READ_GLYPHS() do { \
mapCharactersToGlyphsInFont(currentTable, &characters[currentRun.index], (nextRunStart - currentRun.index), glyphs, &glyphCount); \
mapGlyphsToAdvancesInFont(currentFont, (nextRunStart - currentRun.index), glyphs, advances); \
glyphIdx = 0; \
} while (0)
READ_GLYPHS();
NSMutableCharacterSet *alphaCharset = [NSMutableCharacterSet alphanumericCharacterSet];
[alphaCharset addCharactersInString:@"([{'\"\u2019\u02BC"];
// scan left-to-right looking for newlines or until we hit the width constraint
// When we hit a wrapping point, calculate truncation as follows:
// If we have room to draw at least one more character on the next line, no truncation
// Otherwise apply the truncation algorithm to the current line.
// After calculating any truncation, draw.
// Each time we hit the end of an attribute run, calculate the new font and make sure
// it fits (vertically) within the size constraint. If not, truncate this line.
// When we draw, iterate over the attribute runs for this line and draw each run separately
BOOL lastLine = NO; // used to indicate truncation and to stop the iterating
NSUInteger lineCount = 1;
while (idx < len && !lastLine) {
if (maxLines > 0 && lineCount == maxLines) {
lastLine = YES;
}
// scan left-to-right
struct {
NSUInteger index;
NSUInteger glyphIndex;
NSUInteger currentRunIdx;
} indexCache = { idx, glyphIdx, currentRunIdx };
CGSize lineSize = CGSizeMake(0, currentFont.leading);
CGFloat lineAscender = currentFont.ascender;
struct {
NSUInteger index;
NSUInteger glyphIndex;
NSUInteger currentRunIdx;
CGSize lineSize;
} lastWrapCache = {0, 0, 0, CGSizeZero};
BOOL inAlpha = NO; // used for calculating wrap points
BOOL finishLine = NO;
for (;idx <= len && !finishLine;) {
NSUInteger skipCount = 0;
if (idx == len) {
finishLine = YES;
lastLine = YES;
} else {
if (idx >= nextRunStart) {
// cycle the font and table and grab the next set of glyphs
do {
currentRunIdx++;
READ_RUN();
} while (idx >= nextRunStart);
READ_GLYPHS();
// re-scan the characters to synchronize the glyph index
for (NSUInteger j = currentRun.index; j < idx; j++) {
if (UnicharIsHighSurrogate(characters[j]) && j+1<len && UnicharIsLowSurrogate(characters[j+1])) {
j++;
}
glyphIdx++;
}
if (currentFont.leading > lineSize.height) {
lineSize.height = currentFont.leading;
if (retValue.height + currentFont.ascender > constrainedSize.height) {
lastLine = YES;
finishLine = YES;
}
}
lineAscender = MAX(lineAscender, currentFont.ascender);
}
unichar c = characters[idx];
// Mark a wrap point before spaces and after any stretch of non-alpha characters
BOOL markWrap = NO;
if (c == (unichar)' ') {
markWrap = YES;
} else if ([alphaCharset characterIsMember:c]) {
if (!inAlpha) {
markWrap = YES;
inAlpha = YES;
}
} else {
inAlpha = NO;
}
if (markWrap) {
lastWrapCache = (__typeof__(lastWrapCache)){
.index = idx,
.glyphIndex = glyphIdx,
.currentRunIdx = currentRunIdx,
.lineSize = lineSize
};
}
// process the line
if (c == (unichar)'\n' || c == 0x0085) { // U+0085 is the NEXT_LINE unicode character
finishLine = YES;
skipCount = 1;
} else if (c == (unichar)'\r') {
finishLine = YES;
// check for CRLF
if (idx+1 < len && characters[idx+1] == (unichar)'\n') {
skipCount = 2;
} else {
skipCount = 1;
}
} else if (lineSize.width + advances[glyphIdx] > constrainedSize.width) {
finishLine = YES;
if (retValue.height + lineSize.height + currentFont.ascender > constrainedSize.height) {
lastLine = YES;
}
// walk backwards if wrapping is necessary
if (lastWrapCache.index > indexCache.index && lineBreakMode != UILineBreakModeCharacterWrap &&
(!lastLine || lineBreakMode != UILineBreakModeClip)) {
// we're doing some sort of word wrapping
idx = lastWrapCache.index;
lineSize = lastWrapCache.lineSize;
if (!lastLine) {
// re-check if this is the last line
if (lastWrapCache.currentRunIdx != currentRunIdx) {
currentRunIdx = lastWrapCache.currentRunIdx;
READ_RUN();
READ_GLYPHS();
}
if (retValue.height + lineSize.height + currentFont.ascender > constrainedSize.height) {
lastLine = YES;
}
}
glyphIdx = lastWrapCache.glyphIndex;
// skip any spaces
for (NSUInteger j = idx; j < len && characters[j] == (unichar)' '; j++) {
skipCount++;
}
}
}
}
if (finishLine) {
// TODO: support head/middle truncation
if (lastLine && idx < len && lineBreakMode == UILineBreakModeTailTruncation) {
// truncate
unichar ellipsis = 0x2026; // ellipsis ()
CGGlyph ellipsisGlyph;
mapCharactersToGlyphsInFont(currentTable, &ellipsis, 1, &ellipsisGlyph, NULL);
CGFloat ellipsisWidth;
mapGlyphsToAdvancesInFont(currentFont, 1, &ellipsisGlyph, &ellipsisWidth);
while ((idx - indexCache.index) > 1 && lineSize.width + ellipsisWidth > constrainedSize.width) {
// we have more than 1 character and we're too wide, so back up
idx--;
if (UnicharIsHighSurrogate(characters[idx]) && UnicharIsLowSurrogate(characters[idx+1])) {
idx--;
}
if (idx < currentRun.index) {
ZFont *oldFont = currentFont;
do {
currentRunIdx--;
READ_RUN();
} while (idx < currentRun.index);
READ_GLYPHS();
glyphIdx = glyphCount-1;
if (oldFont != currentFont) {
mapCharactersToGlyphsInFont(currentTable, &ellipsis, 1, &ellipsisGlyph, NULL);
mapGlyphsToAdvancesInFont(currentFont, 1, &ellipsisGlyph, &ellipsisWidth);
}
} else {
glyphIdx--;
}
lineSize.width -= advances[glyphIdx];
}
// skip any spaces before truncating
while ((idx - indexCache.index) > 1 && characters[idx-1] == (unichar)' ') {
idx--;
if (idx < currentRun.index) {
currentRunIdx--;
READ_RUN();
READ_GLYPHS();
glyphIdx = glyphCount-1;
} else {
glyphIdx--;
}
lineSize.width -= advances[glyphIdx];
}
lineSize.width += ellipsisWidth;
glyphs[glyphIdx] = ellipsisGlyph;
idx++;
glyphIdx++;
}
retValue.width = MAX(retValue.width, lineSize.width);
retValue.height += lineSize.height;
// draw
if (performDraw) {
switch (alignment) {
case UITextAlignmentLeft:
drawPoint.x = 0;
break;
case UITextAlignmentCenter:
drawPoint.x = (constrainedSize.width - lineSize.width) / 2.0f;
break;
case UITextAlignmentRight:
drawPoint.x = constrainedSize.width - lineSize.width;
break;
}
NSUInteger stopGlyphIdx = glyphIdx;
NSUInteger lastRunIdx = currentRunIdx;
NSUInteger stopCharIdx = idx;
idx = indexCache.index;
if (currentRunIdx != indexCache.currentRunIdx) {
currentRunIdx = indexCache.currentRunIdx;
READ_RUN();
READ_GLYPHS();
}
glyphIdx = indexCache.glyphIndex;
for (NSUInteger drawIdx = currentRunIdx; drawIdx <= lastRunIdx; drawIdx++) {
if (drawIdx != currentRunIdx) {
currentRunIdx = drawIdx;
READ_RUN();
READ_GLYPHS();
}
NSUInteger numGlyphs;
if (drawIdx == lastRunIdx) {
numGlyphs = stopGlyphIdx - glyphIdx;
idx = stopCharIdx;
} else {
numGlyphs = glyphCount - glyphIdx;
idx = nextRunStart;
}
CGContextSetFont(ctx, currentFont.cgFont);
CGContextSetFontSize(ctx, currentFont.pointSize);
// calculate the fragment size
CGFloat fragmentWidth = 0;
for (NSUInteger g = 0; g < numGlyphs; g++) {
fragmentWidth += advances[glyphIdx + g];
}
if (!ignoreColor) {
UIColor *foregroundColor = getValueOrDefaultForRun(currentRun, ZForegroundColorAttributeName);
UIColor *backgroundColor = getValueOrDefaultForRun(currentRun, ZBackgroundColorAttributeName);
if (backgroundColor != nil && ![backgroundColor isEqual:[UIColor clearColor]]) {
[backgroundColor setFill];
UIRectFillUsingBlendMode((CGRect){ drawPoint, { fragmentWidth, lineSize.height } }, kCGBlendModeNormal);
}
[foregroundColor setFill];
}
CGContextShowGlyphsAtPoint(ctx, drawPoint.x, drawPoint.y + lineAscender, &glyphs[glyphIdx], numGlyphs);
NSNumber *underlineStyle = getValueOrDefaultForRun(currentRun, ZUnderlineStyleAttributeName);
if ([underlineStyle integerValue] & ZUnderlineStyleMask) {
// we only support single for the time being
UIRectFill(CGRectMake(drawPoint.x, drawPoint.y + lineAscender, fragmentWidth, 1));
}
drawPoint.x += fragmentWidth;
glyphIdx += numGlyphs;
}
drawPoint.y += lineSize.height;
}
idx += skipCount;
glyphIdx += skipCount;
lineCount++;
} else {
lineSize.width += advances[glyphIdx];
glyphIdx++;
idx++;
if (idx < len && UnicharIsHighSurrogate(characters[idx-1]) && UnicharIsLowSurrogate(characters[idx])) {
// skip the second half of the surrogate pair
idx++;
}
}
}
}
CFRelease(fontTableMap);
free(glyphs);
free(advances);
free(characters);
#undef READ_GLYPHS
#undef READ_RUN
return retValue;
}
static NSArray *attributeRunForFont(ZFont *font) {
return [NSArray arrayWithObject:[ZAttributeRun attributeRunWithIndex:0
attributes:[NSDictionary dictionaryWithObject:font
forKey:ZFontAttributeName]]];
}
static CGSize drawTextInRect(CGRect rect, NSString *text, NSArray *attributes, UILineBreakMode lineBreakMode,
UITextAlignment alignment, NSUInteger numberOfLines, BOOL ignoreColor) {
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSaveGState(ctx);
// flip it upside-down because our 0,0 is upper-left, whereas ttfs are for screens where 0,0 is lower-left
CGAffineTransform textTransform = CGAffineTransformMake(1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f);
CGContextSetTextMatrix(ctx, textTransform);
CGContextTranslateCTM(ctx, rect.origin.x, rect.origin.y);
CGContextSetTextDrawingMode(ctx, kCGTextFill);
CGSize size = drawOrSizeTextConstrainedToSize(YES, text, attributes, rect.size, numberOfLines, lineBreakMode, alignment, ignoreColor);
CGContextRestoreGState(ctx);
return size;
}
@implementation NSString (FontLabelStringDrawing)
// CGFontRef-based methods
- (CGSize)sizeWithCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize {
return [self sizeWithZFont:[ZFont fontWithCGFont:font size:pointSize]];
}
- (CGSize)sizeWithCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize constrainedToSize:(CGSize)size {
return [self sizeWithZFont:[ZFont fontWithCGFont:font size:pointSize] constrainedToSize:size];
}
- (CGSize)sizeWithCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize constrainedToSize:(CGSize)size
lineBreakMode:(UILineBreakMode)lineBreakMode {
return [self sizeWithZFont:[ZFont fontWithCGFont:font size:pointSize] constrainedToSize:size lineBreakMode:lineBreakMode];
}
- (CGSize)drawAtPoint:(CGPoint)point withCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize {
return [self drawAtPoint:point withZFont:[ZFont fontWithCGFont:font size:pointSize]];
}
- (CGSize)drawInRect:(CGRect)rect withCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize {
return [self drawInRect:rect withZFont:[ZFont fontWithCGFont:font size:pointSize]];
}
- (CGSize)drawInRect:(CGRect)rect withCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize lineBreakMode:(UILineBreakMode)lineBreakMode {
return [self drawInRect:rect withZFont:[ZFont fontWithCGFont:font size:pointSize] lineBreakMode:lineBreakMode];
}
- (CGSize)drawInRect:(CGRect)rect withCGFont:(CGFontRef)font pointSize:(CGFloat)pointSize
lineBreakMode:(UILineBreakMode)lineBreakMode alignment:(UITextAlignment)alignment {
return [self drawInRect:rect withZFont:[ZFont fontWithCGFont:font size:pointSize] lineBreakMode:lineBreakMode alignment:alignment];
}
// ZFont-based methods
- (CGSize)sizeWithZFont:(ZFont *)font {
CGSize size = drawOrSizeTextConstrainedToSize(NO, self, attributeRunForFont(font), CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX), 1,
UILineBreakModeClip, UITextAlignmentLeft, YES);
return CGSizeMake(ceilf(size.width), ceilf(size.height));
}
- (CGSize)sizeWithZFont:(ZFont *)font constrainedToSize:(CGSize)size {
return [self sizeWithZFont:font constrainedToSize:size lineBreakMode:UILineBreakModeWordWrap];
}
/*
According to experimentation with UIStringDrawing, this can actually return a CGSize whose height is greater
than the one passed in. The two cases are as follows:
1. If the given size parameter's height is smaller than a single line, the returned value will
be the height of one line.
2. If the given size parameter's height falls between multiples of a line height, and the wrapped string
actually extends past the size.height, and the difference between size.height and the previous multiple
of a line height is >= the font's ascender, then the returned size's height is extended to the next line.
To put it simply, if the baseline point of a given line falls in the given size, the entire line will
be present in the output size.
*/
- (CGSize)sizeWithZFont:(ZFont *)font constrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode {
size = drawOrSizeTextConstrainedToSize(NO, self, attributeRunForFont(font), size, 0, lineBreakMode, UITextAlignmentLeft, YES);
return CGSizeMake(ceilf(size.width), ceilf(size.height));
}
- (CGSize)sizeWithZFont:(ZFont *)font constrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode
numberOfLines:(NSUInteger)numberOfLines {
size = drawOrSizeTextConstrainedToSize(NO, self, attributeRunForFont(font), size, numberOfLines, lineBreakMode, UITextAlignmentLeft, YES);
return CGSizeMake(ceilf(size.width), ceilf(size.height));
}
- (CGSize)drawAtPoint:(CGPoint)point withZFont:(ZFont *)font {
return [self drawAtPoint:point forWidth:CGFLOAT_MAX withZFont:font lineBreakMode:UILineBreakModeClip];
}
- (CGSize)drawAtPoint:(CGPoint)point forWidth:(CGFloat)width withZFont:(ZFont *)font lineBreakMode:(UILineBreakMode)lineBreakMode {
return drawTextInRect((CGRect){ point, { width, CGFLOAT_MAX } }, self, attributeRunForFont(font), lineBreakMode, UITextAlignmentLeft, 1, YES);
}
- (CGSize)drawInRect:(CGRect)rect withZFont:(ZFont *)font {
return [self drawInRect:rect withZFont:font lineBreakMode:UILineBreakModeWordWrap];
}
- (CGSize)drawInRect:(CGRect)rect withZFont:(ZFont *)font lineBreakMode:(UILineBreakMode)lineBreakMode {
return [self drawInRect:rect withZFont:font lineBreakMode:lineBreakMode alignment:UITextAlignmentLeft];
}
- (CGSize)drawInRect:(CGRect)rect withZFont:(ZFont *)font lineBreakMode:(UILineBreakMode)lineBreakMode
alignment:(UITextAlignment)alignment {
return drawTextInRect(rect, self, attributeRunForFont(font), lineBreakMode, alignment, 0, YES);
}
- (CGSize)drawInRect:(CGRect)rect withZFont:(ZFont *)font lineBreakMode:(UILineBreakMode)lineBreakMode
alignment:(UITextAlignment)alignment numberOfLines:(NSUInteger)numberOfLines {
return drawTextInRect(rect, self, attributeRunForFont(font), lineBreakMode, alignment, numberOfLines, YES);
}
@end
@implementation ZAttributedString (ZAttributedStringDrawing)
- (CGSize)size {
CGSize size = drawOrSizeTextConstrainedToSize(NO, self.string, self.attributes, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX), 1,
UILineBreakModeClip, UITextAlignmentLeft, NO);
return CGSizeMake(ceilf(size.width), ceilf(size.height));
}
- (CGSize)sizeConstrainedToSize:(CGSize)size {
return [self sizeConstrainedToSize:size lineBreakMode:UILineBreakModeWordWrap];
}
- (CGSize)sizeConstrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode {
size = drawOrSizeTextConstrainedToSize(NO, self.string, self.attributes, size, 0, lineBreakMode, UITextAlignmentLeft, NO);
return CGSizeMake(ceilf(size.width), ceilf(size.height));
}
- (CGSize)sizeConstrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode
numberOfLines:(NSUInteger)numberOfLines {
size = drawOrSizeTextConstrainedToSize(NO, self.string, self.attributes, size, numberOfLines, lineBreakMode, UITextAlignmentLeft, NO);
return CGSizeMake(ceilf(size.width), ceilf(size.height));
}
- (CGSize)drawAtPoint:(CGPoint)point {
return [self drawAtPoint:point forWidth:CGFLOAT_MAX lineBreakMode:UILineBreakModeClip];
}
- (CGSize)drawAtPoint:(CGPoint)point forWidth:(CGFloat)width lineBreakMode:(UILineBreakMode)lineBreakMode {
return drawTextInRect((CGRect){ point, { width, CGFLOAT_MAX } }, self.string, self.attributes, lineBreakMode, UITextAlignmentLeft, 1, NO);
}
- (CGSize)drawInRect:(CGRect)rect {
return [self drawInRect:rect withLineBreakMode:UILineBreakModeWordWrap];
}
- (CGSize)drawInRect:(CGRect)rect withLineBreakMode:(UILineBreakMode)lineBreakMode {
return [self drawInRect:rect withLineBreakMode:lineBreakMode alignment:UITextAlignmentLeft];
}
- (CGSize)drawInRect:(CGRect)rect withLineBreakMode:(UILineBreakMode)lineBreakMode alignment:(UITextAlignment)alignment {
return drawTextInRect(rect, self.string, self.attributes, lineBreakMode, alignment, 0, NO);
}
- (CGSize)drawInRect:(CGRect)rect withLineBreakMode:(UILineBreakMode)lineBreakMode alignment:(UITextAlignment)alignment
numberOfLines:(NSUInteger)numberOfLines {
return drawTextInRect(rect, self.string, self.attributes, lineBreakMode, alignment, numberOfLines, NO);
}
@end
@implementation FontLabelStringDrawingHelper
+ (CGSize)sizeWithZFont:(NSString*)string zfont:(ZFont *)font {
CGSize size = drawOrSizeTextConstrainedToSize(NO, string, attributeRunForFont(font), CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX), 1,
UILineBreakModeClip, UITextAlignmentLeft, YES);
return CGSizeMake(ceilf(size.width), ceilf(size.height));
}
+ (CGSize)drawInRect:(NSString*)string rect:(CGRect)rect withZFont:(ZFont *)font lineBreakMode:(UILineBreakMode)lineBreakMode
alignment:(UITextAlignment)alignment {
return [string drawInRect:rect withZFont:font lineBreakMode:lineBreakMode alignment:alignment];
}
@end

View File

@ -0,0 +1,85 @@
//
// FontManager.h
// FontLabel
//
// Created by Kevin Ballard on 5/5/09.
// Copyright © 2009 Zynga Game Networks
//
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#import <Foundation/Foundation.h>
#import <CoreGraphics/CoreGraphics.h>
@class ZFont;
@interface FontManager : NSObject {
CFMutableDictionaryRef fonts;
NSMutableDictionary *urls;
}
+ (FontManager *)sharedManager;
/*!
@method
@abstract Loads a TTF font from the main bundle
@param filename The name of the font file to load (with or without extension).
@return YES if the font was loaded, NO if an error occurred
@discussion If the font has already been loaded, this method does nothing and returns YES.
This method first attempts to load the font by appending .ttf to the filename.
If that file does not exist, it tries the filename exactly as given.
*/
- (BOOL)loadFont:(NSString *)filename;
/*!
@method
@abstract Loads a font from the given file URL
@param url A file URL that points to a font file
@return YES if the font was loaded, NO if an error occurred
@discussion If the font has already been loaded, this method does nothing and returns YES.
*/
- (BOOL)loadFontURL:(NSURL *)url;
/*!
@method
@abstract Returns the loaded font with the given filename
@param filename The name of the font file that was given to -loadFont:
@return A CGFontRef, or NULL if the specified font cannot be found
@discussion If the font has not been loaded yet, -loadFont: will be
called with the given name first.
*/
- (CGFontRef)fontWithName:(NSString *)filename __AVAILABILITY_INTERNAL_DEPRECATED;
/*!
@method
@abstract Returns a ZFont object corresponding to the loaded font with the given filename and point size
@param filename The name of the font file that was given to -loadFont:
@param pointSize The point size of the font
@return A ZFont, or NULL if the specified font cannot be found
@discussion If the font has not been loaded yet, -loadFont: will be
called with the given name first.
*/
- (ZFont *)zFontWithName:(NSString *)filename pointSize:(CGFloat)pointSize;
/*!
@method
@abstract Returns a ZFont object corresponding to the loaded font with the given file URL and point size
@param url A file URL that points to a font file
@param pointSize The point size of the font
@return A ZFont, or NULL if the specified font cannot be loaded
@discussion If the font has not been loaded yet, -loadFontURL: will be called with the given URL first.
*/
- (ZFont *)zFontWithURL:(NSURL *)url pointSize:(CGFloat)pointSize;
/*!
@method
@abstract Returns a CFArrayRef of all loaded CGFont objects
@return A CFArrayRef of all loaded CGFont objects
@description You are responsible for releasing the CFArrayRef
*/
- (CFArrayRef)copyAllFonts;
@end

View File

@ -0,0 +1,123 @@
//
// FontManager.m
// FontLabel
//
// Created by Kevin Ballard on 5/5/09.
// Copyright © 2009 Zynga Game Networks
//
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#import "FontManager.h"
#import "ZFont.h"
static FontManager *sharedFontManager = nil;
@implementation FontManager
+ (FontManager *)sharedManager {
@synchronized(self) {
if (sharedFontManager == nil) {
sharedFontManager = [[self alloc] init];
}
}
return sharedFontManager;
}
- (id)init {
if ((self = [super init])) {
fonts = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
urls = [[NSMutableDictionary alloc] init];
}
return self;
}
- (BOOL)loadFont:(NSString *)filename {
NSString *fontPath = [[NSBundle mainBundle] pathForResource:filename ofType:@"ttf"];
if (fontPath == nil) {
fontPath = [[NSBundle mainBundle] pathForResource:filename ofType:nil];
}
if (fontPath == nil) return NO;
NSURL *url = [NSURL fileURLWithPath:fontPath];
if ([self loadFontURL:url]) {
[urls setObject:url forKey:filename];
return YES;
}
return NO;
}
- (BOOL)loadFontURL:(NSURL *)url {
CGDataProviderRef fontDataProvider = CGDataProviderCreateWithURL((CFURLRef)url);
if (fontDataProvider == NULL) return NO;
CGFontRef newFont = CGFontCreateWithDataProvider(fontDataProvider);
CGDataProviderRelease(fontDataProvider);
if (newFont == NULL) return NO;
CFDictionarySetValue(fonts, url, newFont);
CGFontRelease(newFont);
return YES;
}
- (CGFontRef)fontWithName:(NSString *)filename {
CGFontRef font = NULL;
NSURL *url = [urls objectForKey:filename];
if (url == nil && [self loadFont:filename]) {
url = [urls objectForKey:filename];
}
if (url != nil) {
font = (CGFontRef)CFDictionaryGetValue(fonts, url);
}
return font;
}
- (ZFont *)zFontWithName:(NSString *)filename pointSize:(CGFloat)pointSize {
NSURL *url = [urls objectForKey:filename];
if (url == nil && [self loadFont:filename]) {
url = [urls objectForKey:filename];
}
if (url != nil) {
CGFontRef cgFont = (CGFontRef)CFDictionaryGetValue(fonts, url);
if (cgFont != NULL) {
return [ZFont fontWithCGFont:cgFont size:pointSize];
}
}
return nil;
}
- (ZFont *)zFontWithURL:(NSURL *)url pointSize:(CGFloat)pointSize {
CGFontRef cgFont = (CGFontRef)CFDictionaryGetValue(fonts, url);
if (cgFont == NULL && [self loadFontURL:url]) {
cgFont = (CGFontRef)CFDictionaryGetValue(fonts, url);
}
if (cgFont != NULL) {
return [ZFont fontWithCGFont:cgFont size:pointSize];
}
return nil;
}
- (CFArrayRef)copyAllFonts {
CFIndex count = CFDictionaryGetCount(fonts);
CGFontRef *values = (CGFontRef *)malloc(sizeof(CGFontRef) * count);
CFDictionaryGetKeysAndValues(fonts, NULL, (const void **)values);
CFArrayRef array = CFArrayCreate(NULL, (const void **)values, count, &kCFTypeArrayCallBacks);
free(values);
return array;
}
- (void)dealloc {
CFRelease(fonts);
[urls release];
[super dealloc];
}
@end

View File

@ -0,0 +1,77 @@
//
// ZAttributedString.h
// FontLabel
//
// Created by Kevin Ballard on 9/22/09.
// Copyright 2009 Zynga Game Networks. All rights reserved.
//
#import <Foundation/Foundation.h>
#if NS_BLOCKS_AVAILABLE
#define Z_BLOCKS 1
#else
// set this to 1 if you are using PLBlocks
#define Z_BLOCKS 0
#endif
#if Z_BLOCKS
enum {
ZAttributedStringEnumerationReverse = (1UL << 1),
ZAttributedStringEnumerationLongestEffectiveRangeNotRequired = (1UL << 20)
};
typedef NSUInteger ZAttributedStringEnumerationOptions;
#endif
@interface ZAttributedString : NSObject <NSCoding, NSCopying, NSMutableCopying> {
NSMutableString *_buffer;
NSMutableArray *_attributes;
}
@property (nonatomic, readonly) NSUInteger length;
@property (nonatomic, readonly) NSString *string;
- (id)initWithAttributedString:(ZAttributedString *)attr;
- (id)initWithString:(NSString *)str;
- (id)initWithString:(NSString *)str attributes:(NSDictionary *)attributes;
- (id)attribute:(NSString *)attributeName atIndex:(NSUInteger)index effectiveRange:(NSRangePointer)aRange;
- (id)attribute:(NSString *)attributeName atIndex:(NSUInteger)index longestEffectiveRange:(NSRangePointer)aRange inRange:(NSRange)rangeLimit;
- (ZAttributedString *)attributedSubstringFromRange:(NSRange)aRange;
- (NSDictionary *)attributesAtIndex:(NSUInteger)index effectiveRange:(NSRangePointer)aRange;
- (NSDictionary *)attributesAtIndex:(NSUInteger)index longestEffectiveRange:(NSRangePointer)aRange inRange:(NSRange)rangeLimit;
#if Z_BLOCKS
- (void)enumerateAttribute:(NSString *)attrName inRange:(NSRange)enumerationRange options:(ZAttributedStringEnumerationOptions)opts
usingBlock:(void (^)(id value, NSRange range, BOOL *stop))block;
- (void)enumerateAttributesInRange:(NSRange)enumerationRange options:(ZAttributedStringEnumerationOptions)opts
usingBlock:(void (^)(NSDictionary *attrs, NSRange range, BOOL *stop))block;
#endif
- (BOOL)isEqualToAttributedString:(ZAttributedString *)otherString;
@end
@interface ZMutableAttributedString : ZAttributedString {
}
- (void)addAttribute:(NSString *)name value:(id)value range:(NSRange)range;
- (void)addAttributes:(NSDictionary *)attributes range:(NSRange)range;
- (void)appendAttributedString:(ZAttributedString *)str;
- (void)deleteCharactersInRange:(NSRange)range;
- (void)insertAttributedString:(ZAttributedString *)str atIndex:(NSUInteger)idx;
- (void)removeAttribute:(NSString *)name range:(NSRange)range;
- (void)replaceCharactersInRange:(NSRange)range withAttributedString:(ZAttributedString *)str;
- (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)str;
- (void)setAttributedString:(ZAttributedString *)str;
- (void)setAttributes:(NSDictionary *)attributes range:(NSRange)range;
@end
extern NSString * const ZFontAttributeName;
extern NSString * const ZForegroundColorAttributeName;
extern NSString * const ZBackgroundColorAttributeName;
extern NSString * const ZUnderlineStyleAttributeName;
enum {
ZUnderlineStyleNone = 0x00,
ZUnderlineStyleSingle = 0x01
};
#define ZUnderlineStyleMask 0x00FF
enum {
ZUnderlinePatternSolid = 0x0000
};
#define ZUnderlinePatternMask 0xFF00

View File

@ -0,0 +1,597 @@
//
// ZAttributedString.m
// FontLabel
//
// Created by Kevin Ballard on 9/22/09.
// Copyright 2009 Zynga Game Networks. All rights reserved.
//
#import "ZAttributedString.h"
#import "ZAttributedStringPrivate.h"
@interface ZAttributedString ()
- (NSUInteger)indexOfEffectiveAttributeRunForIndex:(NSUInteger)index;
- (NSDictionary *)attributesAtIndex:(NSUInteger)index effectiveRange:(NSRangePointer)aRange uniquingOnName:(NSString *)attributeName;
- (NSDictionary *)attributesAtIndex:(NSUInteger)index longestEffectiveRange:(NSRangePointer)aRange
inRange:(NSRange)rangeLimit uniquingOnName:(NSString *)attributeName;
@end
@interface ZAttributedString ()
@property (nonatomic, readonly) NSArray *attributes;
@end
@implementation ZAttributedString
@synthesize string = _buffer;
@synthesize attributes = _attributes;
- (id)initWithAttributedString:(ZAttributedString *)attr {
NSParameterAssert(attr != nil);
if ((self = [super init])) {
_buffer = [attr->_buffer mutableCopy];
_attributes = [[NSMutableArray alloc] initWithArray:attr->_attributes copyItems:YES];
}
return self;
}
- (id)initWithString:(NSString *)str {
return [self initWithString:str attributes:nil];
}
- (id)initWithString:(NSString *)str attributes:(NSDictionary *)attributes {
if ((self = [super init])) {
_buffer = [str mutableCopy];
_attributes = [[NSMutableArray alloc] initWithObjects:[ZAttributeRun attributeRunWithIndex:0 attributes:attributes], nil];
}
return self;
}
- (id)init {
return [self initWithString:@"" attributes:nil];
}
- (id)initWithCoder:(NSCoder *)decoder {
if ((self = [super init])) {
_buffer = [[decoder decodeObjectForKey:@"buffer"] mutableCopy];
_attributes = [[decoder decodeObjectForKey:@"attributes"] mutableCopy];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:_buffer forKey:@"buffer"];
[aCoder encodeObject:_attributes forKey:@"attributes"];
}
- (id)copyWithZone:(NSZone *)zone {
return [self retain];
}
- (id)mutableCopyWithZone:(NSZone *)zone {
return [(ZMutableAttributedString *)[ZMutableAttributedString allocWithZone:zone] initWithAttributedString:self];
}
- (NSUInteger)length {
return [_buffer length];
}
- (NSString *)description {
NSMutableArray *components = [NSMutableArray arrayWithCapacity:[_attributes count]*2];
NSRange range = NSMakeRange(0, 0);
for (NSUInteger i = 0; i <= [_attributes count]; i++) {
range.location = NSMaxRange(range);
ZAttributeRun *run;
if (i < [_attributes count]) {
run = [_attributes objectAtIndex:i];
range.length = run.index - range.location;
} else {
run = nil;
range.length = [_buffer length] - range.location;
}
if (range.length > 0) {
[components addObject:[NSString stringWithFormat:@"\"%@\"", [_buffer substringWithRange:range]]];
}
if (run != nil) {
NSMutableArray *attrDesc = [NSMutableArray arrayWithCapacity:[run.attributes count]];
for (id key in run.attributes) {
[attrDesc addObject:[NSString stringWithFormat:@"%@: %@", key, [run.attributes objectForKey:key]]];
}
[components addObject:[NSString stringWithFormat:@"{%@}", [attrDesc componentsJoinedByString:@", "]]];
}
}
return [NSString stringWithFormat:@"%@", [components componentsJoinedByString:@" "]];
}
- (id)attribute:(NSString *)attributeName atIndex:(NSUInteger)index effectiveRange:(NSRangePointer)aRange {
NSParameterAssert(attributeName != nil);
return [[self attributesAtIndex:index effectiveRange:aRange uniquingOnName:attributeName] objectForKey:attributeName];
}
- (id)attribute:(NSString *)attributeName atIndex:(NSUInteger)index longestEffectiveRange:(NSRangePointer)aRange inRange:(NSRange)rangeLimit {
NSParameterAssert(attributeName != nil);
return [[self attributesAtIndex:index longestEffectiveRange:aRange inRange:rangeLimit uniquingOnName:attributeName] objectForKey:attributeName];
}
- (ZAttributedString *)attributedSubstringFromRange:(NSRange)aRange {
if (NSMaxRange(aRange) > [_buffer length]) {
@throw [NSException exceptionWithName:NSRangeException reason:@"range was outisde of the attributed string" userInfo:nil];
}
ZMutableAttributedString *newStr = [self mutableCopy];
if (aRange.location > 0) {
[newStr deleteCharactersInRange:NSMakeRange(0, aRange.location)];
}
if (NSMaxRange(aRange) < [_buffer length]) {
[newStr deleteCharactersInRange:NSMakeRange(aRange.length, [_buffer length] - NSMaxRange(aRange))];
}
return [newStr autorelease];
}
- (NSDictionary *)attributesAtIndex:(NSUInteger)index effectiveRange:(NSRangePointer)aRange {
return [NSDictionary dictionaryWithDictionary:[self attributesAtIndex:index effectiveRange:aRange uniquingOnName:nil]];
}
- (NSDictionary *)attributesAtIndex:(NSUInteger)index longestEffectiveRange:(NSRangePointer)aRange inRange:(NSRange)rangeLimit {
return [NSDictionary dictionaryWithDictionary:[self attributesAtIndex:index longestEffectiveRange:aRange inRange:rangeLimit uniquingOnName:nil]];
}
#if Z_BLOCKS
// Warning: this code has not been tested. The only guarantee is that it compiles.
- (void)enumerateAttribute:(NSString *)attrName inRange:(NSRange)enumerationRange options:(ZAttributedStringEnumerationOptions)opts
usingBlock:(void (^)(id, NSRange, BOOL*))block {
if (opts & ZAttributedStringEnumerationLongestEffectiveRangeNotRequired) {
[self enumerateAttributesInRange:enumerationRange options:opts usingBlock:^(NSDictionary *attrs, NSRange range, BOOL *stop) {
id value = [attrs objectForKey:attrName];
if (value != nil) {
block(value, range, stop);
}
}];
} else {
__block id oldValue = nil;
__block NSRange effectiveRange = NSMakeRange(0, 0);
[self enumerateAttributesInRange:enumerationRange options:opts usingBlock:^(NSDictionary *attrs, NSRange range, BOOL *stop) {
id value = [attrs objectForKey:attrName];
if (oldValue == nil) {
oldValue = value;
effectiveRange = range;
} else if (value != nil && [oldValue isEqual:value]) {
// combine the attributes
effectiveRange = NSUnionRange(effectiveRange, range);
} else {
BOOL innerStop = NO;
block(oldValue, effectiveRange, &innerStop);
if (innerStop) {
*stop = YES;
oldValue = nil;
} else {
oldValue = value;
}
}
}];
if (oldValue != nil) {
BOOL innerStop = NO; // necessary for the block, but unused
block(oldValue, effectiveRange, &innerStop);
}
}
}
- (void)enumerateAttributesInRange:(NSRange)enumerationRange options:(ZAttributedStringEnumerationOptions)opts
usingBlock:(void (^)(NSDictionary*, NSRange, BOOL*))block {
// copy the attributes so we can mutate the string if necessary during enumeration
// also clip the array during copy to only the subarray of attributes that cover the requested range
NSArray *attrs;
if (NSEqualRanges(enumerationRange, NSMakeRange(0, 0))) {
attrs = [NSArray arrayWithArray:_attributes];
} else {
// in this binary search, last is the first run after the range
NSUInteger first = 0, last = [_attributes count];
while (last > first+1) {
NSUInteger pivot = (last + first) / 2;
ZAttributeRun *run = [_attributes objectAtIndex:pivot];
if (run.index < enumerationRange.location) {
first = pivot;
} else if (run.index >= NSMaxRange(enumerationRange)) {
last = pivot;
}
}
attrs = [_attributes subarrayWithRange:NSMakeRange(first, last-first)];
}
if (opts & ZAttributedStringEnumerationReverse) {
NSUInteger end = [_buffer length];
for (ZAttributeRun *run in [attrs reverseObjectEnumerator]) {
BOOL stop = NO;
NSUInteger start = run.index;
// clip to enumerationRange
start = MAX(start, enumerationRange.location);
end = MIN(end, NSMaxRange(enumerationRange));
block(run.attributes, NSMakeRange(start, end - start), &stop);
if (stop) break;
end = run.index;
}
} else {
NSUInteger start = 0;
ZAttributeRun *run = [attrs objectAtIndex:0];
NSInteger offset = 0;
NSInteger oldLength = [_buffer length];
for (NSUInteger i = 1;;i++) {
NSUInteger end;
if (i >= [attrs count]) {
end = oldLength;
} else {
end = [[attrs objectAtIndex:i] index];
}
BOOL stop = NO;
NSUInteger clippedStart = MAX(start, enumerationRange.location);
NSUInteger clippedEnd = MIN(end, NSMaxRange(enumerationRange));
block(run.attributes, NSMakeRange(clippedStart + offset, clippedEnd - start), &stop);
if (stop || i >= [attrs count]) break;
start = end;
NSUInteger newLength = [_buffer length];
offset += (newLength - oldLength);
oldLength = newLength;
}
}
}
#endif
- (BOOL)isEqualToAttributedString:(ZAttributedString *)otherString {
return ([_buffer isEqualToString:otherString->_buffer] && [_attributes isEqualToArray:otherString->_attributes]);
}
- (BOOL)isEqual:(id)object {
return [object isKindOfClass:[ZAttributedString class]] && [self isEqualToAttributedString:(ZAttributedString *)object];
}
#pragma mark -
- (NSUInteger)indexOfEffectiveAttributeRunForIndex:(NSUInteger)index {
NSUInteger first = 0, last = [_attributes count];
while (last > first + 1) {
NSUInteger pivot = (last + first) / 2;
ZAttributeRun *run = [_attributes objectAtIndex:pivot];
if (run.index > index) {
last = pivot;
} else if (run.index < index) {
first = pivot;
} else {
first = pivot;
break;
}
}
return first;
}
- (NSDictionary *)attributesAtIndex:(NSUInteger)index effectiveRange:(NSRangePointer)aRange uniquingOnName:(NSString *)attributeName {
if (index >= [_buffer length]) {
@throw [NSException exceptionWithName:NSRangeException reason:@"index beyond range of attributed string" userInfo:nil];
}
NSUInteger runIndex = [self indexOfEffectiveAttributeRunForIndex:index];
ZAttributeRun *run = [_attributes objectAtIndex:runIndex];
if (aRange != NULL) {
aRange->location = run.index;
runIndex++;
if (runIndex < [_attributes count]) {
aRange->length = [[_attributes objectAtIndex:runIndex] index] - aRange->location;
} else {
aRange->length = [_buffer length] - aRange->location;
}
}
return run.attributes;
}
- (NSDictionary *)attributesAtIndex:(NSUInteger)index longestEffectiveRange:(NSRangePointer)aRange
inRange:(NSRange)rangeLimit uniquingOnName:(NSString *)attributeName {
if (index >= [_buffer length]) {
@throw [NSException exceptionWithName:NSRangeException reason:@"index beyond range of attributed string" userInfo:nil];
} else if (NSMaxRange(rangeLimit) > [_buffer length]) {
@throw [NSException exceptionWithName:NSRangeException reason:@"rangeLimit beyond range of attributed string" userInfo:nil];
}
NSUInteger runIndex = [self indexOfEffectiveAttributeRunForIndex:index];
ZAttributeRun *run = [_attributes objectAtIndex:runIndex];
if (aRange != NULL) {
if (attributeName != nil) {
id value = [run.attributes objectForKey:attributeName];
NSUInteger endRunIndex = runIndex+1;
runIndex--;
// search backwards
while (1) {
if (run.index <= rangeLimit.location) {
break;
}
ZAttributeRun *prevRun = [_attributes objectAtIndex:runIndex];
id prevValue = [prevRun.attributes objectForKey:attributeName];
if (prevValue == value || (value != nil && [prevValue isEqual:value])) {
runIndex--;
run = prevRun;
} else {
break;
}
}
// search forwards
ZAttributeRun *endRun = nil;
while (endRunIndex < [_attributes count]) {
ZAttributeRun *nextRun = [_attributes objectAtIndex:endRunIndex];
if (nextRun.index >= NSMaxRange(rangeLimit)) {
endRun = nextRun;
break;
}
id nextValue = [nextRun.attributes objectForKey:attributeName];
if (nextValue == value || (value != nil && [nextValue isEqual:value])) {
endRunIndex++;
} else {
endRun = nextRun;
break;
}
}
aRange->location = MAX(run.index, rangeLimit.location);
aRange->length = MIN((endRun ? endRun.index : [_buffer length]), NSMaxRange(rangeLimit)) - aRange->location;
} else {
// with no attribute name, we don't need to do any real searching,
// as we already guarantee each run has unique attributes.
// just make sure to clip the range to the rangeLimit
aRange->location = MAX(run.index, rangeLimit.location);
ZAttributeRun *endRun = (runIndex+1 < [_attributes count] ? [_attributes objectAtIndex:runIndex+1] : nil);
aRange->length = MIN((endRun ? endRun.index : [_buffer length]), NSMaxRange(rangeLimit)) - aRange->location;
}
}
return run.attributes;
}
- (void)dealloc {
[_buffer release];
[_attributes release];
[super dealloc];
}
@end
@interface ZMutableAttributedString ()
- (void)cleanupAttributesInRange:(NSRange)range;
- (NSRange)rangeOfAttributeRunsForRange:(NSRange)range;
- (void)offsetRunsInRange:(NSRange )range byOffset:(NSInteger)offset;
@end
@implementation ZMutableAttributedString
- (id)copyWithZone:(NSZone *)zone {
return [(ZAttributedString *)[ZAttributedString allocWithZone:zone] initWithAttributedString:self];
}
- (void)addAttribute:(NSString *)name value:(id)value range:(NSRange)range {
range = [self rangeOfAttributeRunsForRange:range];
for (ZAttributeRun *run in [_attributes subarrayWithRange:range]) {
[run.attributes setObject:value forKey:name];
}
[self cleanupAttributesInRange:range];
}
- (void)addAttributes:(NSDictionary *)attributes range:(NSRange)range {
range = [self rangeOfAttributeRunsForRange:range];
for (ZAttributeRun *run in [_attributes subarrayWithRange:range]) {
[run.attributes addEntriesFromDictionary:attributes];
}
[self cleanupAttributesInRange:range];
}
- (void)appendAttributedString:(ZAttributedString *)str {
[self insertAttributedString:str atIndex:[_buffer length]];
}
- (void)deleteCharactersInRange:(NSRange)range {
NSRange runRange = [self rangeOfAttributeRunsForRange:range];
[_buffer replaceCharactersInRange:range withString:@""];
[_attributes removeObjectsInRange:runRange];
for (NSUInteger i = runRange.location; i < [_attributes count]; i++) {
ZAttributeRun *run = [_attributes objectAtIndex:i];
ZAttributeRun *newRun = [[ZAttributeRun alloc] initWithIndex:(run.index - range.length) attributes:run.attributes];
[_attributes replaceObjectAtIndex:i withObject:newRun];
[newRun release];
}
[self cleanupAttributesInRange:NSMakeRange(runRange.location, 0)];
}
- (void)insertAttributedString:(ZAttributedString *)str atIndex:(NSUInteger)idx {
[self replaceCharactersInRange:NSMakeRange(idx, 0) withAttributedString:str];
}
- (void)removeAttribute:(NSString *)name range:(NSRange)range {
range = [self rangeOfAttributeRunsForRange:range];
for (ZAttributeRun *run in [_attributes subarrayWithRange:range]) {
[run.attributes removeObjectForKey:name];
}
[self cleanupAttributesInRange:range];
}
- (void)replaceCharactersInRange:(NSRange)range withAttributedString:(ZAttributedString *)str {
NSRange replaceRange = [self rangeOfAttributeRunsForRange:range];
NSInteger offset = [str->_buffer length] - range.length;
[_buffer replaceCharactersInRange:range withString:str->_buffer];
[_attributes replaceObjectsInRange:replaceRange withObjectsFromArray:str->_attributes];
NSRange newRange = NSMakeRange(replaceRange.location, [str->_attributes count]);
[self offsetRunsInRange:newRange byOffset:range.location];
[self offsetRunsInRange:NSMakeRange(NSMaxRange(newRange), [_attributes count] - NSMaxRange(newRange)) byOffset:offset];
[self cleanupAttributesInRange:NSMakeRange(newRange.location, 0)];
[self cleanupAttributesInRange:NSMakeRange(NSMaxRange(newRange), 0)];
}
- (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)str {
[self replaceCharactersInRange:range withAttributedString:[[[ZAttributedString alloc] initWithString:str] autorelease]];
}
- (void)setAttributedString:(ZAttributedString *)str {
[_buffer release], _buffer = [str->_buffer mutableCopy];
[_attributes release], _attributes = [str->_attributes mutableCopy];
}
- (void)setAttributes:(NSDictionary *)attributes range:(NSRange)range {
range = [self rangeOfAttributeRunsForRange:range];
for (ZAttributeRun *run in [_attributes subarrayWithRange:range]) {
[run.attributes setDictionary:attributes];
}
[self cleanupAttributesInRange:range];
}
#pragma mark -
// splits the existing runs to provide one or more new runs for the given range
- (NSRange)rangeOfAttributeRunsForRange:(NSRange)range {
NSParameterAssert(NSMaxRange(range) <= [_buffer length]);
// find (or create) the first run
NSUInteger first = 0;
ZAttributeRun *lastRun = nil;
for (;;first++) {
if (first >= [_attributes count]) {
// we didn't find a run
first = [_attributes count];
ZAttributeRun *newRun = [[ZAttributeRun alloc] initWithIndex:range.location attributes:lastRun.attributes];
[_attributes addObject:newRun];
[newRun release];
break;
}
ZAttributeRun *run = [_attributes objectAtIndex:first];
if (run.index == range.location) {
break;
} else if (run.index > range.location) {
ZAttributeRun *newRun = [[ZAttributeRun alloc] initWithIndex:range.location attributes:lastRun.attributes];
[_attributes insertObject:newRun atIndex:first];
[newRun release];
break;
}
lastRun = run;
}
if (((ZAttributeRun *)[_attributes lastObject]).index < NSMaxRange(range)) {
NSRange subrange = NSMakeRange(first, [_attributes count] - first);
if (NSMaxRange(range) < [_buffer length]) {
ZAttributeRun *newRun = [[ZAttributeRun alloc] initWithIndex:NSMaxRange(range)
attributes:(NSDictionary*)[(ZAttributeRun *)[_attributes lastObject] attributes]];
[_attributes addObject:newRun];
[newRun release];
}
return subrange;
} else {
// find the last run within and the first run after the range
NSUInteger lastIn = first, firstAfter = [_attributes count]-1;
while (firstAfter > lastIn + 1) {
NSUInteger idx = (firstAfter + lastIn) / 2;
ZAttributeRun *run = [_attributes objectAtIndex:idx];
if (run.index < range.location) {
lastIn = idx;
} else if (run.index > range.location) {
firstAfter = idx;
} else {
// this is definitively the first run after the range
firstAfter = idx;
break;
}
}
if ([[_attributes objectAtIndex:firstAfter] index] > NSMaxRange(range)) {
// the first after is too far after, insert another run!
ZAttributeRun *newRun = [[ZAttributeRun alloc] initWithIndex:NSMaxRange(range)
attributes:[(ZAttributeRun *)[_attributes objectAtIndex:firstAfter-1] attributes]];
[_attributes insertObject:newRun atIndex:firstAfter];
[newRun release];
}
return NSMakeRange(lastIn, firstAfter - lastIn);
}
}
- (void)cleanupAttributesInRange:(NSRange)range {
// expand the range to include one surrounding attribute on each side
if (range.location > 0) {
range.location -= 1;
range.length += 1;
}
if (NSMaxRange(range) < [_attributes count]) {
range.length += 1;
} else {
// make sure the range is capped to the attributes count
range.length = [_attributes count] - range.location;
}
if (range.length == 0) return;
ZAttributeRun *lastRun = [_attributes objectAtIndex:range.location];
for (NSUInteger i = range.location+1; i < NSMaxRange(range);) {
ZAttributeRun *run = [_attributes objectAtIndex:i];
if ([lastRun.attributes isEqualToDictionary:run.attributes]) {
[_attributes removeObjectAtIndex:i];
range.length -= 1;
} else {
lastRun = run;
i++;
}
}
}
- (void)offsetRunsInRange:(NSRange)range byOffset:(NSInteger)offset {
for (NSUInteger i = range.location; i < NSMaxRange(range); i++) {
ZAttributeRun *run = [_attributes objectAtIndex:i];
ZAttributeRun *newRun = [[ZAttributeRun alloc] initWithIndex:run.index + offset attributes:run.attributes];
[_attributes replaceObjectAtIndex:i withObject:newRun];
[newRun release];
}
}
@end
@implementation ZAttributeRun
@synthesize index = _index;
@synthesize attributes = _attributes;
+ (id)attributeRunWithIndex:(NSUInteger)idx attributes:(NSDictionary *)attrs {
return [[[self alloc] initWithIndex:idx attributes:attrs] autorelease];
}
- (id)initWithIndex:(NSUInteger)idx attributes:(NSDictionary *)attrs {
NSParameterAssert(idx >= 0);
if ((self = [super init])) {
_index = idx;
if (attrs == nil) {
_attributes = [[NSMutableDictionary alloc] init];
} else {
_attributes = [attrs mutableCopy];
}
}
return self;
}
- (id)initWithCoder:(NSCoder *)decoder {
if ((self = [super init])) {
_index = [[decoder decodeObjectForKey:@"index"] unsignedIntegerValue];
_attributes = [[decoder decodeObjectForKey:@"attributes"] mutableCopy];
}
return self;
}
- (id)init {
return [self initWithIndex:0 attributes:[NSDictionary dictionary]];
}
- (id)copyWithZone:(NSZone *)zone {
return [[ZAttributeRun allocWithZone:zone] initWithIndex:_index attributes:_attributes];
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:[NSNumber numberWithUnsignedInteger:_index] forKey:@"index"];
[aCoder encodeObject:_attributes forKey:@"attributes"];
}
- (NSString *)description {
NSMutableArray *components = [NSMutableArray arrayWithCapacity:[_attributes count]];
for (id key in _attributes) {
[components addObject:[NSString stringWithFormat:@"%@=%@", key, [_attributes objectForKey:key]]];
}
return [NSString stringWithFormat:@"<%@: %p index=%lu attributes={%@}>",
NSStringFromClass([self class]), self, (unsigned long)_index, [components componentsJoinedByString:@" "]];
}
- (BOOL)isEqual:(id)object {
if (![object isKindOfClass:[ZAttributeRun class]]) return NO;
ZAttributeRun *other = (ZAttributeRun *)object;
return _index == other->_index && [_attributes isEqualToDictionary:other->_attributes];
}
- (void)dealloc {
[_attributes release];
[super dealloc];
}
@end
NSString * const ZFontAttributeName = @"ZFontAttributeName";
NSString * const ZForegroundColorAttributeName = @"ZForegroundColorAttributeName";
NSString * const ZBackgroundColorAttributeName = @"ZBackgroundColorAttributeName";
NSString * const ZUnderlineStyleAttributeName = @"ZUnderlineStyleAttributeName";

View File

@ -0,0 +1,24 @@
//
// ZAttributedStringPrivate.h
// FontLabel
//
// Created by Kevin Ballard on 9/23/09.
// Copyright 2009 Zynga Game Networks. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "ZAttributedString.h"
@interface ZAttributeRun : NSObject <NSCopying, NSCoding> {
NSUInteger _index;
NSMutableDictionary *_attributes;
}
@property (nonatomic, readonly) NSUInteger index;
@property (nonatomic, readonly) NSMutableDictionary *attributes;
+ (id)attributeRunWithIndex:(NSUInteger)idx attributes:(NSDictionary *)attrs;
- (id)initWithIndex:(NSUInteger)idx attributes:(NSDictionary *)attrs;
@end
@interface ZAttributedString (ZAttributedStringPrivate)
@property (nonatomic, readonly) NSArray *attributes;
@end

View File

@ -0,0 +1,47 @@
//
// ZFont.h
// FontLabel
//
// Created by Kevin Ballard on 7/2/09.
// Copyright © 2009 Zynga Game Networks
//
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface ZFont : NSObject {
CGFontRef _cgFont;
CGFloat _pointSize;
CGFloat _ratio;
NSString *_familyName;
NSString *_fontName;
NSString *_postScriptName;
}
@property (nonatomic, readonly) CGFontRef cgFont;
@property (nonatomic, readonly) CGFloat pointSize;
@property (nonatomic, readonly) CGFloat ascender;
@property (nonatomic, readonly) CGFloat descender;
@property (nonatomic, readonly) CGFloat leading;
@property (nonatomic, readonly) CGFloat xHeight;
@property (nonatomic, readonly) CGFloat capHeight;
@property (nonatomic, readonly) NSString *familyName;
@property (nonatomic, readonly) NSString *fontName;
@property (nonatomic, readonly) NSString *postScriptName;
+ (ZFont *)fontWithCGFont:(CGFontRef)cgFont size:(CGFloat)fontSize;
+ (ZFont *)fontWithUIFont:(UIFont *)uiFont;
- (id)initWithCGFont:(CGFontRef)cgFont size:(CGFloat)fontSize;
- (ZFont *)fontWithSize:(CGFloat)fontSize;
@end

View File

@ -0,0 +1,170 @@
//
// ZFont.m
// FontLabel
//
// Created by Kevin Ballard on 7/2/09.
// Copyright © 2009 Zynga Game Networks
//
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#import "ZFont.h"
@interface ZFont ()
@property (nonatomic, readonly) CGFloat ratio;
- (NSString *)copyNameTableEntryForID:(UInt16)nameID;
@end
@implementation ZFont
@synthesize cgFont=_cgFont, pointSize=_pointSize, ratio=_ratio;
+ (ZFont *)fontWithCGFont:(CGFontRef)cgFont size:(CGFloat)fontSize {
return [[[self alloc] initWithCGFont:cgFont size:fontSize] autorelease];
}
+ (ZFont *)fontWithUIFont:(UIFont *)uiFont {
NSParameterAssert(uiFont != nil);
CGFontRef cgFont = CGFontCreateWithFontName((CFStringRef)uiFont.fontName);
ZFont *zFont = [[self alloc] initWithCGFont:cgFont size:uiFont.pointSize];
CGFontRelease(cgFont);
return [zFont autorelease];
}
- (id)initWithCGFont:(CGFontRef)cgFont size:(CGFloat)fontSize {
if ((self = [super init])) {
_cgFont = CGFontRetain(cgFont);
_pointSize = fontSize;
_ratio = fontSize/CGFontGetUnitsPerEm(cgFont);
}
return self;
}
- (id)init {
NSAssert(NO, @"-init is not valid for ZFont");
return nil;
}
- (CGFloat)ascender {
return ceilf(self.ratio * CGFontGetAscent(self.cgFont));
}
- (CGFloat)descender {
return floorf(self.ratio * CGFontGetDescent(self.cgFont));
}
- (CGFloat)leading {
return (self.ascender - self.descender);
}
- (CGFloat)capHeight {
return ceilf(self.ratio * CGFontGetCapHeight(self.cgFont));
}
- (CGFloat)xHeight {
return ceilf(self.ratio * CGFontGetXHeight(self.cgFont));
}
- (NSString *)familyName {
if (_familyName == nil) {
_familyName = [self copyNameTableEntryForID:1];
}
return _familyName;
}
- (NSString *)fontName {
if (_fontName == nil) {
_fontName = [self copyNameTableEntryForID:4];
}
return _fontName;
}
- (NSString *)postScriptName {
if (_postScriptName == nil) {
_postScriptName = [self copyNameTableEntryForID:6];
}
return _postScriptName;
}
- (ZFont *)fontWithSize:(CGFloat)fontSize {
if (fontSize == self.pointSize) return self;
NSParameterAssert(fontSize > 0.0);
return [[[ZFont alloc] initWithCGFont:self.cgFont size:fontSize] autorelease];
}
- (BOOL)isEqual:(id)object {
if (![object isKindOfClass:[ZFont class]]) return NO;
ZFont *font = (ZFont *)object;
return (font.cgFont == self.cgFont && font.pointSize == self.pointSize);
}
- (NSString *)copyNameTableEntryForID:(UInt16)aNameID {
CFDataRef nameTable = CGFontCopyTableForTag(self.cgFont, 'name');
NSAssert1(nameTable != NULL, @"CGFontCopyTableForTag returned NULL for 'name' tag in font %@",
[(id)CFCopyDescription(self.cgFont) autorelease]);
const UInt8 * const bytes = CFDataGetBytePtr(nameTable);
NSAssert1(OSReadBigInt16(bytes, 0) == 0, @"name table for font %@ has bad version number",
[(id)CFCopyDescription(self.cgFont) autorelease]);
const UInt16 count = OSReadBigInt16(bytes, 2);
const UInt16 stringOffset = OSReadBigInt16(bytes, 4);
const UInt8 * const nameRecords = &bytes[6];
UInt16 nameLength = 0;
UInt16 nameOffset = 0;
NSStringEncoding encoding = 0;
for (UInt16 idx = 0; idx < count; idx++) {
const uintptr_t recordOffset = 12 * idx;
const UInt16 nameID = OSReadBigInt16(nameRecords, recordOffset + 6);
if (nameID != aNameID) continue;
const UInt16 platformID = OSReadBigInt16(nameRecords, recordOffset + 0);
const UInt16 platformSpecificID = OSReadBigInt16(nameRecords, recordOffset + 2);
encoding = 0;
// for now, we only support a subset of encodings
switch (platformID) {
case 0: // Unicode
encoding = NSUTF16StringEncoding;
break;
case 1: // Macintosh
switch (platformSpecificID) {
case 0:
encoding = NSMacOSRomanStringEncoding;
break;
}
case 3: // Microsoft
switch (platformSpecificID) {
case 1:
encoding = NSUTF16StringEncoding;
break;
}
}
if (encoding == 0) continue;
nameLength = OSReadBigInt16(nameRecords, recordOffset + 8);
nameOffset = OSReadBigInt16(nameRecords, recordOffset + 10);
break;
}
NSString *result = nil;
if (nameOffset > 0) {
const UInt8 *nameBytes = &bytes[stringOffset + nameOffset];
result = [[NSString alloc] initWithBytes:nameBytes length:nameLength encoding:encoding];
}
CFRelease(nameTable);
return result;
}
- (void)dealloc {
CGFontRelease(_cgFont);
[_familyName release];
[_fontName release];
[_postScriptName release];
[super dealloc];
}
@end

View File

@ -1 +1 @@
6616770f7d0487bf12f076293d27d5ef5f11af1e
5bef454f11ee915de4be9b28176a431b4fb96eed

View File

@ -1 +1 @@
55a23e272c4ae52bb22a27f5bd0f4309bcb4c683
8ece3a97993ed9c38a8b35727a870c5e33b8b54d

View File

@ -1 +1 @@
e927644074613ed3644c897bdb0912660335380e
e02526afe0e6e03feb5abbe2ee127cacceb0ded2

View File

@ -1 +1 @@
99f6132989ba063c7d343a1b0b27799df3d2f280
4f2f642918e859dde3db5a24ebb9484634f1d2fc

View File

@ -52,6 +52,7 @@ LOCAL_SRC_FILES := main.cpp \
../../../tests/EaseActionsTest/EaseActionsTest.cpp \
../../../tests/EffectsAdvancedTest/EffectsAdvancedTest.cpp \
../../../tests/EffectsTest/EffectsTest.cpp \
../../../tests/FontTest/FontTest.cpp \
../../../tests/HiResTest/HiResTest.cpp \
../../../tests/IntervalTest/IntervalTest.cpp \
../../../tests/KeypadTest/KeypadTest.cpp \

View File

@ -83,6 +83,9 @@ public class Cocos2dxActivity extends Activity{
backgroundMusicPlayer = new Cocos2dxMusic(this);
soundPlayer = new Cocos2dxSound(this);
// init bitmap context
Cocos2dxBitmap.setContext(this);
handler = new Handler(){
public void handleMessage(Message msg){
switch(msg.what){

View File

@ -4,6 +4,7 @@ import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.LinkedList;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
@ -11,6 +12,7 @@ import android.graphics.Paint;
import android.graphics.Typeface;
import android.graphics.Paint.Align;
import android.graphics.Paint.FontMetricsInt;
import android.util.Log;
public class Cocos2dxBitmap{
/*
@ -21,6 +23,12 @@ public class Cocos2dxBitmap{
private static final int ALIGNLEFT = 0x31;
private static final int ALIGNRIGHT = 0x32;
private static Context context;
public static void setContext(Context context){
Cocos2dxBitmap.context = context;
}
/*
* @width: the width to draw, it can be 0
* @height: the height to draw, it can be 0
@ -31,7 +39,7 @@ public class Cocos2dxBitmap{
content = refactorString(content);
Paint paint = newPaint(fontName, fontSize, alignment);
TextProperty textProperty = getTextWidthAndHeight(content, paint, width, height);
TextProperty textProperty = computeTextProperty(content, paint, width, height);
// Draw text to bitmap
Bitmap bitmap = Bitmap.createBitmap(textProperty.maxWidth,
@ -95,7 +103,7 @@ public class Cocos2dxBitmap{
}
}
private static TextProperty getTextWidthAndHeight(String content, Paint paint,
private static TextProperty computeTextProperty(String content, Paint paint,
int maxWidth, int maxHeight){
FontMetricsInt fm = paint.getFontMetricsInt();
int h = (int)Math.ceil(fm.descent - fm.ascent);
@ -119,7 +127,6 @@ public class Cocos2dxBitmap{
}
}
return new TextProperty(maxContentWidth, h, lines);
}
@ -244,9 +251,29 @@ public class Cocos2dxBitmap{
Paint paint = new Paint();
paint.setColor(Color.WHITE);
paint.setTextSize(fontSize);
paint.setTypeface(Typeface.create(fontName, Typeface.NORMAL));
paint.setAntiAlias(true);
/*
* Set type face for paint, now it support .ttf file.
*/
if (fontName.endsWith(".ttf")){
try {
Typeface typeFace = Typeface.createFromAsset(context.getAssets(), fontName);
paint.setTypeface(typeFace);
} catch (Exception e){
Log.e("Cocos2dxBitmap",
"error to create ttf type face: " + fontName);
/*
* The file may not find, use system font
*/
paint.setTypeface(Typeface.create(fontName, Typeface.NORMAL));
}
}
else {
paint.setTypeface(Typeface.create(fontName, Typeface.NORMAL));
}
switch (alignment){
case ALIGNCENTER:
paint.setTextAlign(Align.CENTER);

View File

@ -1 +1 @@
898c0f4be35b091c881554d3a75aafca0788707a
089538e21098633ff475d29aa721c886c2eed848

View File

@ -19,15 +19,14 @@
* SOFTWARE.
*/
#include "chipmunk.h"
#include "drawSpace.h"
#include "cocos2d.h"
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <limits.h>
#include <string.h>
#include "chipmunk.h"
#include "drawSpace.h"
#include "cocos2d.h"
using namespace cocos2d;

View File

@ -0,0 +1,133 @@
#include "FontTest.h"
#include "../testResource.h"
enum {
kTagLabel1,
kTagLabel2,
kTagLabel3,
kTagLabel4,
};
static int fontIdx = 0;
#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
static std::string fontList[] =
{
"American Typewriter",
"Marker Felt",
"A Damn Mess",
"Abberancy",
"Abduction",
"Paint Boy",
"Schwarzwald Regular",
"Scissor Cuts",
};
#else
static std::string fontList[] =
{
"fonts/A Damn Mess.ttf",
"fonts/Abberancy.ttf",
"fonts/Abduction.ttf",
"fonts/Paint Boy.ttf",
"fonts/Schwarzwald Regular.ttf",
"fonts/Scissor Cuts.ttf",
};
#endif
static const char* nextAction(void)
{
fontIdx++;
fontIdx = fontIdx % (sizeof(fontList) / sizeof(fontList[0]));
return fontList[fontIdx].c_str();
}
static const char* backAction(void)
{
fontIdx--;
if (fontIdx < 0)
{
fontIdx += (sizeof(fontList) / sizeof(fontList[0]));
}
return fontList[fontIdx].c_str();
}
static const char* restartAction(void)
{
return fontList[fontIdx].c_str();
}
FontTest::FontTest()
{
CCSize size = CCDirector::sharedDirector()->getWinSize();
CCMenuItemImage *item1 = CCMenuItemImage::itemFromNormalImage(s_pPathB1, s_pPathB2, this, menu_selector(FontTest::backCallback));
CCMenuItemImage *item2 = CCMenuItemImage::itemFromNormalImage(s_pPathR1, s_pPathR2, this, menu_selector(FontTest::restartCallback));
CCMenuItemImage *item3 = CCMenuItemImage::itemFromNormalImage(s_pPathF1, s_pPathF2, this, menu_selector(FontTest::nextCallback));
CCMenu *menu = CCMenu::menuWithItems(item1, item2, item3, NULL);
menu->setPosition(CCPointZero);
item1->setPosition(ccp(size.width/2 - 100,30));
item2->setPosition(ccp(size.width/2, 30));
item3->setPosition(ccp(size.width/2 + 100,30));
addChild(menu, 1);
showFont(restartAction());
}
void FontTest::showFont(const char *pFont)
{
removeChildByTag(kTagLabel1, true);
removeChildByTag(kTagLabel2, true);
removeChildByTag(kTagLabel3, true);
removeChildByTag(kTagLabel4, true);
CCSize s = CCDirector::sharedDirector()->getWinSize();
CCLabelTTF *top = CCLabelTTF::labelWithString(pFont, pFont, 24);
CCLabelTTF *left = CCLabelTTF::labelWithString("alignment left", CCSizeMake(s.width, 50), CCTextAlignmentLeft, pFont, 32);
CCLabelTTF *center = CCLabelTTF::labelWithString("alignment center", CCSizeMake(s.width, 50), CCTextAlignmentCenter, pFont, 32);
CCLabelTTF *right = CCLabelTTF::labelWithString("alignment right", CCSizeMake(s.width, 50), CCTextAlignmentRight, pFont, 32);
top->setPosition(ccp(s.width/2, 250));
left->setPosition(ccp(s.width/2, 200));
center->setPosition(ccp(s.width/2, 150));
right->setPosition(ccp(s.width/2, 100));
addChild(left, 0, kTagLabel1);
addChild(right, 0, kTagLabel2);
addChild(center, 0, kTagLabel3);
addChild(top, 0, kTagLabel4);
}
void FontTest::backCallback(CCObject* pSender)
{
showFont(backAction());
}
void FontTest::nextCallback(CCObject* pSender)
{
showFont(nextAction());
}
std::string FontTest::title()
{
return "Font test";
}
void FontTest::restartCallback(CCObject* pSender)
{
showFont(restartAction());
}
///---------------------------------------
//
// DirectorTestScene
//
///---------------------------------------
void FontTestScene::runThisTest()
{
CCLayer* pLayer = FontTest::node();
addChild(pLayer);
CCDirector::sharedDirector()->replaceScene(this);
}

View File

@ -0,0 +1,27 @@
#ifndef _FONT_TEST_H_
#define _FONT_TEST_H_
#include "cocos2d.h"
#include "../testBasic.h"
class FontTestScene : public TestScene
{
public:
virtual void runThisTest();
};
class FontTest : public cocos2d::CCLayer
{
public:
FontTest();
void showFont(const char *pFont);
void restartCallback(CCObject* pSender);
void nextCallback(CCObject* pSender);
void backCallback(CCObject* pSender);
virtual std::string title();
LAYER_NODE_FUNC(FontTest);
};
#endif // _FONT_TEST_H_

View File

@ -106,6 +106,10 @@ static TestScene* CreateTestScene(int nIdx)
pScene = new DirectorTestScene(); break;
case TEST_BUGS:
pScene = new BugsTestScene(); break;
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
case TEST_FONTS:
pScene = new FontTestScene(); break;
#endif
default:
break;
}

View File

@ -42,6 +42,7 @@
#include "DirectorTest/DirectorTest.h"
#include "BugsTest/BugsTest.h"
#include "Texture2dTest/Texture2dTest.h"
#include "FontTest/FontTest.h"
#if (CC_TARGET_PLATFORM != CC_PLATFORM_AIRPLAY)
#include "ChipmunkTest/cocos2dChipmunkDemo.h"
@ -94,6 +95,7 @@ enum
TEST_USERDEFAULT,
TEST_DIRECTOR,
TEST_BUGS,
TEST_FONTS,
TESTS_COUNT,
};
@ -137,7 +139,8 @@ const std::string g_aTestNames[TESTS_COUNT] = {
"CurlTest",
"UserDefaultTest",
"DirectorTest",
"BugsTest"
"BugsTest",
"FontTest"
};
#endif