mirror of https://github.com/axmolengine/axmol.git
commit
6715ee03d7
|
@ -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){
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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){
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -1 +1 @@
|
|||
5abcfaee751bdf7cdcd592e7e566090b500eddca
|
||||
a94593d95f2fff9766c611a35a6593a87d490698
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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, ¤tRun, &nextRunStart, \
|
||||
¤tFont, ¤tTable)
|
||||
|
||||
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
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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";
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -1 +1 @@
|
|||
6616770f7d0487bf12f076293d27d5ef5f11af1e
|
||||
5bef454f11ee915de4be9b28176a431b4fb96eed
|
|
@ -1 +1 @@
|
|||
55a23e272c4ae52bb22a27f5bd0f4309bcb4c683
|
||||
8ece3a97993ed9c38a8b35727a870c5e33b8b54d
|
|
@ -1 +1 @@
|
|||
e927644074613ed3644c897bdb0912660335380e
|
||||
e02526afe0e6e03feb5abbe2ee127cacceb0ded2
|
|
@ -1 +1 @@
|
|||
99f6132989ba063c7d343a1b0b27799df3d2f280
|
||||
4f2f642918e859dde3db5a24ebb9484634f1d2fc
|
|
@ -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 \
|
||||
|
|
|
@ -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){
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -1 +1 @@
|
|||
898c0f4be35b091c881554d3a75aafca0788707a
|
||||
089538e21098633ff475d29aa721c886c2eed848
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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_
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue