mirror of https://github.com/axmolengine/axmol.git
commit
6715ee03d7
|
@ -83,6 +83,9 @@ public class Cocos2dxActivity extends Activity{
|
||||||
backgroundMusicPlayer = new Cocos2dxMusic(this);
|
backgroundMusicPlayer = new Cocos2dxMusic(this);
|
||||||
soundPlayer = new Cocos2dxSound(this);
|
soundPlayer = new Cocos2dxSound(this);
|
||||||
|
|
||||||
|
// init bitmap context
|
||||||
|
Cocos2dxBitmap.setContext(this);
|
||||||
|
|
||||||
handler = new Handler(){
|
handler = new Handler(){
|
||||||
public void handleMessage(Message msg){
|
public void handleMessage(Message msg){
|
||||||
switch(msg.what){
|
switch(msg.what){
|
||||||
|
|
|
@ -4,6 +4,7 @@ import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
|
@ -11,6 +12,7 @@ import android.graphics.Paint;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
import android.graphics.Paint.Align;
|
import android.graphics.Paint.Align;
|
||||||
import android.graphics.Paint.FontMetricsInt;
|
import android.graphics.Paint.FontMetricsInt;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
public class Cocos2dxBitmap{
|
public class Cocos2dxBitmap{
|
||||||
/*
|
/*
|
||||||
|
@ -21,6 +23,12 @@ public class Cocos2dxBitmap{
|
||||||
private static final int ALIGNLEFT = 0x31;
|
private static final int ALIGNLEFT = 0x31;
|
||||||
private static final int ALIGNRIGHT = 0x32;
|
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
|
* @width: the width to draw, it can be 0
|
||||||
* @height: the height to draw, it can be 0
|
* @height: the height to draw, it can be 0
|
||||||
|
@ -243,9 +251,31 @@ public class Cocos2dxBitmap{
|
||||||
private static Paint newPaint(String fontName, int fontSize, int alignment){
|
private static Paint newPaint(String fontName, int fontSize, int alignment){
|
||||||
Paint paint = new Paint();
|
Paint paint = new Paint();
|
||||||
paint.setColor(Color.WHITE);
|
paint.setColor(Color.WHITE);
|
||||||
paint.setTextSize(fontSize);
|
paint.setTextSize(fontSize);
|
||||||
paint.setTypeface(Typeface.create(fontName, Typeface.NORMAL));
|
|
||||||
paint.setAntiAlias(true);
|
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){
|
switch (alignment){
|
||||||
case ALIGNCENTER:
|
case ALIGNCENTER:
|
||||||
|
|
|
@ -83,6 +83,9 @@ public class Cocos2dxActivity extends Activity{
|
||||||
backgroundMusicPlayer = new Cocos2dxMusic(this);
|
backgroundMusicPlayer = new Cocos2dxMusic(this);
|
||||||
soundPlayer = new Cocos2dxSound(this);
|
soundPlayer = new Cocos2dxSound(this);
|
||||||
|
|
||||||
|
// init bitmap context
|
||||||
|
Cocos2dxBitmap.setContext(this);
|
||||||
|
|
||||||
handler = new Handler(){
|
handler = new Handler(){
|
||||||
public void handleMessage(Message msg){
|
public void handleMessage(Message msg){
|
||||||
switch(msg.what){
|
switch(msg.what){
|
||||||
|
|
|
@ -4,6 +4,7 @@ import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
|
@ -11,6 +12,7 @@ import android.graphics.Paint;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
import android.graphics.Paint.Align;
|
import android.graphics.Paint.Align;
|
||||||
import android.graphics.Paint.FontMetricsInt;
|
import android.graphics.Paint.FontMetricsInt;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
public class Cocos2dxBitmap{
|
public class Cocos2dxBitmap{
|
||||||
/*
|
/*
|
||||||
|
@ -21,6 +23,12 @@ public class Cocos2dxBitmap{
|
||||||
private static final int ALIGNLEFT = 0x31;
|
private static final int ALIGNLEFT = 0x31;
|
||||||
private static final int ALIGNRIGHT = 0x32;
|
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
|
* @width: the width to draw, it can be 0
|
||||||
* @height: the height to draw, it can be 0
|
* @height: the height to draw, it can be 0
|
||||||
|
@ -117,8 +125,7 @@ public class Cocos2dxBitmap{
|
||||||
maxContentWidth = temp;
|
maxContentWidth = temp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return new TextProperty(maxContentWidth, h, lines);
|
return new TextProperty(maxContentWidth, h, lines);
|
||||||
}
|
}
|
||||||
|
@ -243,9 +250,29 @@ public class Cocos2dxBitmap{
|
||||||
private static Paint newPaint(String fontName, int fontSize, int alignment){
|
private static Paint newPaint(String fontName, int fontSize, int alignment){
|
||||||
Paint paint = new Paint();
|
Paint paint = new Paint();
|
||||||
paint.setColor(Color.WHITE);
|
paint.setColor(Color.WHITE);
|
||||||
paint.setTextSize(fontSize);
|
paint.setTextSize(fontSize);
|
||||||
paint.setTypeface(Typeface.create(fontName, Typeface.NORMAL));
|
|
||||||
paint.setAntiAlias(true);
|
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){
|
switch (alignment){
|
||||||
case ALIGNCENTER:
|
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
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
#include <Foundation/Foundation.h>
|
#import "CCImage.h"
|
||||||
#include <UIKit/UIKit.h>
|
#import "CCFileUtils.h"
|
||||||
#include "CCImage.h"
|
#import <string>
|
||||||
#include "CCFileUtils.h"
|
|
||||||
#include <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
|
typedef struct
|
||||||
{
|
{
|
||||||
|
@ -335,104 +342,97 @@ static bool _initWithString(const char * pText, cocos2d::CCImage::ETextAlign eAl
|
||||||
{
|
{
|
||||||
CC_BREAK_IF(! pText || ! pInfo);
|
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
|
// create the font
|
||||||
NSString * fntName = _isValidFontName(pFontName) ? [NSString stringWithUTF8String:pFontName] : @"MarkerFelt-Wide";
|
id font;
|
||||||
UIFont * font = [UIFont fontWithName:fntName size:nSize];
|
font = [UIFont fontWithName:fntName size:nSize];
|
||||||
if (! font)
|
if (font)
|
||||||
{
|
{
|
||||||
font = [UIFont systemFontOfSize:nSize];
|
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];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
CC_BREAK_IF(! font);
|
|
||||||
|
|
||||||
// measure text size with specified font and determine the rectangle to draw text in
|
if (pInfo->width != 0 || pInfo->height != 0)
|
||||||
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
|
dim.width = pInfo->width;
|
||||||
CGSize contentSize = CGSizeMake(0x7fffffff, 0x7fffffff);
|
dim.height = pInfo->height;
|
||||||
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
|
else
|
||||||
{
|
{
|
||||||
// the content height no limit, use 0x7fffffff as a big enough height
|
pInfo->width = dim.width;
|
||||||
CGSize contentSize = CGSizeMake(pInfo->width, 0x7fffffff);
|
pInfo->height = dim.height;
|
||||||
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
|
CC_BREAK_IF(! font);
|
||||||
size_t width = (size_t)canvasSize.width;
|
|
||||||
size_t height = (size_t)canvasSize.height;
|
unsigned char* data = new unsigned char[pInfo->width * pInfo->height * 4];
|
||||||
unsigned char* data = new unsigned char[width * height * 4];
|
memset(data, 0, pInfo->width * pInfo->height * 4);
|
||||||
CC_BREAK_IF(! data);
|
|
||||||
memset(data, 0, height * width * 4);
|
|
||||||
|
|
||||||
// draw text
|
// draw text
|
||||||
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
|
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
|
||||||
|
CGContextRef context = CGBitmapContextCreate(data, dim.width, dim.height, 8, dim.width * 4, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
|
||||||
CGContextRef context = CGBitmapContextCreate(data, width, height, 8, width * 4, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
|
|
||||||
CGColorSpaceRelease(colorSpace);
|
CGColorSpaceRelease(colorSpace);
|
||||||
CC_BREAK_IF(!context);
|
|
||||||
|
if (! context)
|
||||||
|
{
|
||||||
|
delete[] data;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
CGContextSetRGBFillColor(context, 1, 1, 1, 1);
|
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
|
CGContextScaleCTM(context, 1.0f, -1.0f); //NOTE: NSString draws in UIKit referential i.e. renders upside-down compared to CGBitmapContext referential
|
||||||
UIGraphicsPushContext(context);
|
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
|
UITextAlignment align = (2 == uHoriFlag) ? UITextAlignmentRight
|
||||||
: (3 == uHoriFlag) ? UITextAlignmentCenter
|
: (3 == uHoriFlag) ? UITextAlignmentCenter
|
||||||
: UITextAlignmentLeft;
|
: 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();
|
UIGraphicsPopContext();
|
||||||
|
|
||||||
|
@ -473,7 +473,12 @@ bool CCImage::initWithImageFile(const char * strPath, EImageFormat eImgFmt/* = e
|
||||||
return initWithImageData(data.getBuffer(), data.getSize(), eImgFmt);
|
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;
|
bool bRet = false;
|
||||||
tImageInfo info = {0};
|
tImageInfo info = {0};
|
||||||
|
@ -520,5 +525,10 @@ bool CCImage::initWithString(
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CCImage::saveToFile(const char *pszFilePath, bool bIsToRGB)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
NS_CC_END;
|
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/EaseActionsTest/EaseActionsTest.cpp \
|
||||||
../../../tests/EffectsAdvancedTest/EffectsAdvancedTest.cpp \
|
../../../tests/EffectsAdvancedTest/EffectsAdvancedTest.cpp \
|
||||||
../../../tests/EffectsTest/EffectsTest.cpp \
|
../../../tests/EffectsTest/EffectsTest.cpp \
|
||||||
|
../../../tests/FontTest/FontTest.cpp \
|
||||||
../../../tests/HiResTest/HiResTest.cpp \
|
../../../tests/HiResTest/HiResTest.cpp \
|
||||||
../../../tests/IntervalTest/IntervalTest.cpp \
|
../../../tests/IntervalTest/IntervalTest.cpp \
|
||||||
../../../tests/KeypadTest/KeypadTest.cpp \
|
../../../tests/KeypadTest/KeypadTest.cpp \
|
||||||
|
|
|
@ -83,6 +83,9 @@ public class Cocos2dxActivity extends Activity{
|
||||||
backgroundMusicPlayer = new Cocos2dxMusic(this);
|
backgroundMusicPlayer = new Cocos2dxMusic(this);
|
||||||
soundPlayer = new Cocos2dxSound(this);
|
soundPlayer = new Cocos2dxSound(this);
|
||||||
|
|
||||||
|
// init bitmap context
|
||||||
|
Cocos2dxBitmap.setContext(this);
|
||||||
|
|
||||||
handler = new Handler(){
|
handler = new Handler(){
|
||||||
public void handleMessage(Message msg){
|
public void handleMessage(Message msg){
|
||||||
switch(msg.what){
|
switch(msg.what){
|
||||||
|
|
|
@ -4,6 +4,7 @@ import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
|
@ -11,6 +12,7 @@ import android.graphics.Paint;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
import android.graphics.Paint.Align;
|
import android.graphics.Paint.Align;
|
||||||
import android.graphics.Paint.FontMetricsInt;
|
import android.graphics.Paint.FontMetricsInt;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
public class Cocos2dxBitmap{
|
public class Cocos2dxBitmap{
|
||||||
/*
|
/*
|
||||||
|
@ -21,6 +23,12 @@ public class Cocos2dxBitmap{
|
||||||
private static final int ALIGNLEFT = 0x31;
|
private static final int ALIGNLEFT = 0x31;
|
||||||
private static final int ALIGNRIGHT = 0x32;
|
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
|
* @width: the width to draw, it can be 0
|
||||||
* @height: the height 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);
|
content = refactorString(content);
|
||||||
Paint paint = newPaint(fontName, fontSize, alignment);
|
Paint paint = newPaint(fontName, fontSize, alignment);
|
||||||
|
|
||||||
TextProperty textProperty = getTextWidthAndHeight(content, paint, width, height);
|
TextProperty textProperty = computeTextProperty(content, paint, width, height);
|
||||||
|
|
||||||
// Draw text to bitmap
|
// Draw text to bitmap
|
||||||
Bitmap bitmap = Bitmap.createBitmap(textProperty.maxWidth,
|
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){
|
int maxWidth, int maxHeight){
|
||||||
FontMetricsInt fm = paint.getFontMetricsInt();
|
FontMetricsInt fm = paint.getFontMetricsInt();
|
||||||
int h = (int)Math.ceil(fm.descent - fm.ascent);
|
int h = (int)Math.ceil(fm.descent - fm.ascent);
|
||||||
|
@ -119,7 +127,6 @@ public class Cocos2dxBitmap{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return new TextProperty(maxContentWidth, h, lines);
|
return new TextProperty(maxContentWidth, h, lines);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -243,10 +250,30 @@ public class Cocos2dxBitmap{
|
||||||
private static Paint newPaint(String fontName, int fontSize, int alignment){
|
private static Paint newPaint(String fontName, int fontSize, int alignment){
|
||||||
Paint paint = new Paint();
|
Paint paint = new Paint();
|
||||||
paint.setColor(Color.WHITE);
|
paint.setColor(Color.WHITE);
|
||||||
paint.setTextSize(fontSize);
|
paint.setTextSize(fontSize);
|
||||||
paint.setTypeface(Typeface.create(fontName, Typeface.NORMAL));
|
paint.setAntiAlias(true);
|
||||||
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){
|
switch (alignment){
|
||||||
case ALIGNCENTER:
|
case ALIGNCENTER:
|
||||||
paint.setTextAlign(Align.CENTER);
|
paint.setTextAlign(Align.CENTER);
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
898c0f4be35b091c881554d3a75aafca0788707a
|
089538e21098633ff475d29aa721c886c2eed848
|
|
@ -19,15 +19,14 @@
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "chipmunk.h"
|
|
||||||
#include "drawSpace.h"
|
|
||||||
#include "cocos2d.h"
|
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include "chipmunk.h"
|
||||||
|
#include "drawSpace.h"
|
||||||
|
#include "cocos2d.h"
|
||||||
|
|
||||||
using namespace cocos2d;
|
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;
|
pScene = new DirectorTestScene(); break;
|
||||||
case TEST_BUGS:
|
case TEST_BUGS:
|
||||||
pScene = new BugsTestScene(); break;
|
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:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,6 +42,7 @@
|
||||||
#include "DirectorTest/DirectorTest.h"
|
#include "DirectorTest/DirectorTest.h"
|
||||||
#include "BugsTest/BugsTest.h"
|
#include "BugsTest/BugsTest.h"
|
||||||
#include "Texture2dTest/Texture2dTest.h"
|
#include "Texture2dTest/Texture2dTest.h"
|
||||||
|
#include "FontTest/FontTest.h"
|
||||||
|
|
||||||
#if (CC_TARGET_PLATFORM != CC_PLATFORM_AIRPLAY)
|
#if (CC_TARGET_PLATFORM != CC_PLATFORM_AIRPLAY)
|
||||||
#include "ChipmunkTest/cocos2dChipmunkDemo.h"
|
#include "ChipmunkTest/cocos2dChipmunkDemo.h"
|
||||||
|
@ -94,6 +95,7 @@ enum
|
||||||
TEST_USERDEFAULT,
|
TEST_USERDEFAULT,
|
||||||
TEST_DIRECTOR,
|
TEST_DIRECTOR,
|
||||||
TEST_BUGS,
|
TEST_BUGS,
|
||||||
|
TEST_FONTS,
|
||||||
|
|
||||||
TESTS_COUNT,
|
TESTS_COUNT,
|
||||||
};
|
};
|
||||||
|
@ -137,7 +139,8 @@ const std::string g_aTestNames[TESTS_COUNT] = {
|
||||||
"CurlTest",
|
"CurlTest",
|
||||||
"UserDefaultTest",
|
"UserDefaultTest",
|
||||||
"DirectorTest",
|
"DirectorTest",
|
||||||
"BugsTest"
|
"BugsTest",
|
||||||
|
"FontTest"
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Reference in New Issue