Instantly share code, notes, and snippets.

Embed
What would you like to do?
A category for Sparrow 2.X that allows a bitmap SPTextField to justify align.
//
// SPTextField+Justify.h
// Sparrow 2.X
//
// Created by Shilo White on 7/16/13.
//
#import "SPTextField.h"
#define SPVAlignJustify SPVAlignBottom+1
#define SPHAlignJustify SPHAlignRight+1
#define SPHAlignJustifyLastLineLeft SPHAlignJustify+1
#define SPHAlignJustifyLastLineCenter SPHAlignJustifyLastLineLeft+1
#define SPHAlignJustifyLastLineRight SPHAlignJustifyLastLineCenter+1
//
// SPTextField+Justify.m
// Sparrow 2.X
//
// Created by Shilo White on 7/16/13.
//
#import "SPTextField+Justify.h"
#import "SPBitmapFont.h"
#define CHAR_SPACE 32
#define CHAR_TAB 9
#define CHAR_NEWLINE 10
#define CHAR_CARRIAGE_RETURN 13
@interface SPTextField ()
{
float _fontSize;
uint _color;
NSString *_text;
NSString *_fontName;
SPHAlign _hAlign;
SPVAlign _vAlign;
BOOL _autoScale;
SPQuadBatch *_contents;
SPRectangle *_textBounds;
SPQuad *_hitArea;
}
@end
@implementation SPTextField (Justified)
- (void)createRenderedContents
{
float width = _hitArea.width;
float height = _hitArea.height;
float fontSize = _fontSize == SP_NATIVE_FONT_SIZE ? SP_DEFAULT_FONT_SIZE : _fontSize;
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_6_0
NSLineBreakMode lbm = NSLineBreakByTruncatingTail;
#else
UILineBreakMode lbm = UILineBreakModeTailTruncation;
#endif
CGSize textSize;
if (_autoScale)
{
CGSize maxSize = CGSizeMake(width, FLT_MAX);
fontSize += 1.0f;
do
{
fontSize -= 1.0f;
textSize = [_text sizeWithFont:[UIFont fontWithName:_fontName size:fontSize]
constrainedToSize:maxSize lineBreakMode:lbm];
} while (textSize.height > height);
}
else
{
textSize = [_text sizeWithFont:[UIFont fontWithName:_fontName size:fontSize]
constrainedToSize:CGSizeMake(width, height) lineBreakMode:lbm];
}
float xOffset = 0;
if (_hAlign == SPHAlignCenter) xOffset = (width - textSize.width) / 2.0f;
else if (_hAlign == SPHAlignRight) xOffset = width - textSize.width;
float yOffset = 0;
if (_vAlign == SPVAlignCenter) yOffset = (height - textSize.height) / 2.0f;
else if (_vAlign == SPVAlignBottom) yOffset = height - textSize.height;
if (!_textBounds) _textBounds = [[SPRectangle alloc] init];
[_textBounds setX:xOffset y:yOffset width:textSize.width height:textSize.height];
SPTexture *texture = [[SPTexture alloc] initWithWidth:width height:height generateMipmaps:YES
draw:^(CGContextRef context)
{
float red = SP_COLOR_PART_RED(_color) / 255.0f;
float green = SP_COLOR_PART_GREEN(_color) / 255.0f;
float blue = SP_COLOR_PART_BLUE(_color) / 255.0f;
CGContextSetRGBFillColor(context, red, green, blue, 1.0f);
if (_hAlign >= SPHAlignJustify) NSLog(@"[WARNING] SPTextField SPHAlignJustify is not supported for native font support. (Use bitmap fonts instead)");
if (_vAlign >= SPVAlignJustify) NSLog(@"[WARNING] SPTextField SPVAlignJustify is not supported for native font support. (Use bitmap fonts instead)");
[_text drawInRect:CGRectMake(0, yOffset, width, height)
withFont:[UIFont fontWithName:_fontName size:fontSize]
lineBreakMode:lbm alignment:(UITextAlignment)(_hAlign == SPHAlignJustify)?SPHAlignLeft:_hAlign];
}];
SPImage *image = [[SPImage alloc] initWithTexture:texture];
[_contents addQuad:image];
}
@end
@interface SPCharLocation2 : SPPoolObject
@property (nonatomic) SPBitmapChar *bitmapChar;
@property (nonatomic) float scale;
@property (nonatomic) float x;
@property (nonatomic) float y;
- (id)initWithChar:(SPBitmapChar *)bitmapChar;
@end
@implementation SPCharLocation2
- (id)initWithChar:(SPBitmapChar *)bitmapChar
{
if ((self = [super init]))
_bitmapChar = bitmapChar;
return self;
}
SP_IMPLEMENT_MEMORY_POOL();
@end
@interface SPBitmapFont ()
{
float _size;
float _lineHeight;
}
@end
@implementation SPBitmapFont (Justify)
- (NSMutableArray *)arrangeCharsInAreaWithWidth:(float)width height:(float)height
text:(NSString *)text fontSize:(float)size
hAlign:(SPHAlign)hAlign vAlign:(SPVAlign)vAlign
autoScale:(BOOL)autoScale kerning:(BOOL)kerning
{
if (text.length == 0) return [NSMutableArray array];
if (size < 0) size *= -_size;
NSMutableArray *lines;
NSMutableArray *whitespaces;
float scale;
float containerWidth;
float containerHeight;
BOOL finished = NO;
BOOL isVAlignJustify = (vAlign == SPVAlignJustify);
BOOL isHAlignJustify = (hAlign == SPHAlignJustify ||
hAlign == SPHAlignJustifyLastLineLeft ||
hAlign == SPHAlignJustifyLastLineCenter ||
hAlign == SPHAlignJustifyLastLineRight);
while (!finished)
{
lines = [NSMutableArray array];
if (isHAlignJustify)
whitespaces = [NSMutableArray array];
scale = size / _size;
containerWidth = width / scale;
containerHeight = height / scale;
if (_lineHeight <= containerHeight)
{
uint numOfWhiteSpaces = 0;
int lastWhiteSpace = -1;
int lastCharID = -1;
int numChars = text.length;
float currentX = 0;
float currentY = 0;
NSMutableArray *currentLine = [NSMutableArray array];
for (int i=0; i<numChars; i++)
{
BOOL lineFull = NO;
int charID = [text characterAtIndex:i];
SPBitmapChar *bitmapChar = [self charByID:charID];
if (charID == CHAR_NEWLINE || charID == CHAR_CARRIAGE_RETURN)
{
lineFull = YES;
}
else if (!bitmapChar)
{
NSLog(@"Missing character: %d", charID);
}
else
{
if (charID == CHAR_SPACE || charID == CHAR_TAB) {
lastWhiteSpace = i;
if (isHAlignJustify)
numOfWhiteSpaces++;
}
if (kerning)
currentX += [bitmapChar kerningToChar:lastCharID];
SPCharLocation2 *charLocation = [[SPCharLocation2 alloc] initWithChar:bitmapChar];
charLocation.x = currentX + bitmapChar.xOffset;
charLocation.y = currentY + bitmapChar.yOffset;
[currentLine addObject:charLocation];
currentX += bitmapChar.xAdvance;
lastCharID = charID;
if (charLocation.x + bitmapChar.width > containerWidth)
{
// remove characters and add them again to next line
int numCharsToRemove = lastWhiteSpace == -1 ? 1 : i - lastWhiteSpace;
int removeIndex = currentLine.count - numCharsToRemove;
[currentLine removeObjectsInRange:NSMakeRange(removeIndex, numCharsToRemove)];
if (currentLine.count == 0)
break;
i -= numCharsToRemove;
lineFull = YES;
}
}
if (i == numChars - 1)
{
if (isHAlignJustify) {
[whitespaces addObject:[NSNumber numberWithUnsignedInt:numOfWhiteSpaces]];
numOfWhiteSpaces = 0;
}
[lines addObject:currentLine];
finished = YES;
}
else if (lineFull)
{
[lines addObject:currentLine];
if (lastWhiteSpace == i) {
[currentLine removeLastObject];
if (isHAlignJustify)
numOfWhiteSpaces--;
}
if (isHAlignJustify) {
[whitespaces addObject:[NSNumber numberWithUnsignedInt:numOfWhiteSpaces]];
numOfWhiteSpaces = 0;
}
if (currentY + 2*_lineHeight <= containerHeight)
{
currentLine = [NSMutableArray array];
currentX = 0.0f;
currentY += _lineHeight;
lastWhiteSpace = -1;
lastCharID = -1;
}
else
{
break;
}
}
} // for each char
} // if (_lineHeight < containerHeight)
if (autoScale && !finished)
{
size -= 1;
[lines removeAllObjects];
}
else
{
finished = YES;
}
} // while (!finished)
NSMutableArray *finalLocations = [NSMutableArray array];
int numLines = lines.count;
float bottom = numLines * _lineHeight;
int yOffset = 0;
if (vAlign == SPVAlignBottom) yOffset = containerHeight - bottom;
else if (vAlign == SPVAlignCenter) yOffset = (containerHeight - bottom) / 2;
for (NSArray *line in lines)
{
int numChars = line.count;
if (!numChars) continue;
int xOffset = 0;
SPCharLocation2 *lastLocation = [line lastObject];
float right = lastLocation.x - lastLocation.bitmapChar.xOffset
+ lastLocation.bitmapChar.xAdvance;
if (hAlign == SPHAlignRight) xOffset = containerWidth - right;
else if (hAlign == SPHAlignCenter) xOffset = (containerWidth - right) / 2;
for (SPCharLocation2 *charLocation in line)
{
charLocation.x = scale * (charLocation.x + xOffset);
charLocation.y = scale * (charLocation.y + yOffset);
charLocation.scale = scale;
if (charLocation.bitmapChar.width > 0 && charLocation.bitmapChar.height > 0)
[finalLocations addObject:charLocation];
}
}
if (isHAlignJustify || isVAlignJustify)
{
float bottom = 0.0f;
float bottomSpacing = 0.0f;
float yOffset = 0.0f;
uint numOfLineSpaces = 0;
if (isVAlignJustify)
{
bottom = (lines.count * _lineHeight) * scale;
bottomSpacing = height - bottom;
numOfLineSpaces = MAX(lines.count-1, 0);
}
for (NSMutableArray *line in lines)
{
float right = 0.0f;
float rightSpacing = 0.0f;
float xOffset = 0.0f;
uint numOfWhiteSpaces = 0;
if (isHAlignJustify && line.count) {
SPCharLocation2 *lastLocation = [line lastObject];
right = lastLocation.x + (lastLocation.bitmapChar.width * scale);
rightSpacing = width - right;
numOfWhiteSpaces = [[whitespaces objectAtIndex:[lines indexOfObject:line]] unsignedIntValue];
}
BOOL hAlignLineIndependent = ([lines indexOfObject:line] == lines.count-1) && ((hAlign >= SPHAlignJustifyLastLineLeft));
if (hAlignLineIndependent) {
if (hAlign == SPHAlignJustifyLastLineLeft) xOffset = 0;
else if (hAlign == SPHAlignJustifyLastLineCenter) xOffset = rightSpacing/2;
else if (hAlign == SPHAlignJustifyLastLineRight) xOffset = rightSpacing;
}
if (numOfLineSpaces || numOfWhiteSpaces)
{
for (SPCharLocation2 *charLocation in line)
{
if (hAlignLineIndependent) {
charLocation.x += xOffset;
} else if (numOfWhiteSpaces) {
charLocation.x += xOffset;
int charID = charLocation.bitmapChar.charID;
if (charID == CHAR_SPACE || charID == CHAR_TAB)
xOffset += rightSpacing/numOfWhiteSpaces;
}
if (numOfLineSpaces)
charLocation.y += yOffset;
}
if (numOfLineSpaces)
yOffset += bottomSpacing/numOfLineSpaces;
}
}
}
return finalLocations;
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment