// // SemiSecretText.m // flixel-ios // // Copyright Semi Secret Software 2009-2010. All rights reserved. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // #import #define PADDING 5.0 @interface SemiSecretText () - (void) computeNewBounds; - (NSInteger) nextWrapOffsetForGlyphs:(NSInteger)offset; @end @implementation SemiSecretText @dynamic font; @synthesize color, text, effect, align, shadowColor; @synthesize wrapped; @synthesize lineSpacing; @synthesize reposition; @synthesize padding; @synthesize wrapOnSpace; @synthesize debug; + (id) text { return [[[self alloc] initWithFrame:CGRectZero] autorelease]; } - (NSString *) description { return [NSString stringWithFormat:@"", self.text, self.bounds.origin.x, self.bounds.origin.y, self.bounds.size.width, self.bounds.size.height]; } - (id) initWithFrame:(CGRect)frame { if ([super initWithFrame:frame] == nil) return nil; wrapOnSpace = YES; padding = PADDING; reposition = YES; color = CGColorRetain([UIColor whiteColor].CGColor); shadowColor = CGColorRetain([UIColor blackColor].CGColor); text = @""; self.font = [SemiSecretFont fontWithName:@"Flixel" size:8.0]; self.opaque = NO; effect = SemiSecretTextEffectNone; align = SemiSecretTextAlignCenter; self.userInteractionEnabled = NO; lineSpacing = 1.6; return self; } - (void) dealloc { [text release]; CGColorRelease(color); CGColorRelease(shadowColor); self.font = nil; if (glyphs) free(glyphs); [super dealloc]; } - (void) setPadding:(CGFloat)newPadding { if (padding == newPadding) return; padding = newPadding; [self computeNewBounds]; [self setNeedsDisplay]; } - (void) setText:(NSString *)newText { if (text == newText) { return; } [text autorelease]; text = [newText copy]; [self computeNewBounds]; [self setNeedsDisplay]; } - (void) setFont:(id)newFont { if (!newFont) { //release useUIFont = NO; useSSFont = NO; [ssfont release]; [uifont release]; ssfont = nil; uifont = nil; [self computeNewBounds]; [self setNeedsDisplay]; return; } //what is the type? if ([newFont isKindOfClass:[UIFont class]]) { useSSFont = NO; useUIFont = YES; if (uifont == newFont) return; [uifont release]; uifont = newFont; [uifont retain]; [self computeNewBounds]; [self setNeedsDisplay]; } else if ([newFont isKindOfClass:[SemiSecretFont class]]) { useSSFont = YES; useUIFont = NO; if (ssfont == newFont) return; [ssfont release]; ssfont = newFont; [ssfont retain]; [self computeNewBounds]; [self setNeedsDisplay]; } } - (id) font { if (useSSFont) return ssfont; if (useUIFont) return uifont; return nil; } - (void) setColor:(CGColorRef)newColor { if (color == newColor) return; CGColorRelease(color); color = newColor; CGColorRetain(color); [self setNeedsDisplay]; } - (void) setShadowColor:(CGColorRef)newColor { if (shadowColor == newColor) return; CGColorRelease(shadowColor); shadowColor = newColor; CGColorRetain(shadowColor); [self setNeedsDisplay]; } - (void) setAlign:(SemiSecretTextAlign)Align { if (align == Align) return; align = Align; [self setNeedsDisplay]; } - (void) setEffect:(SemiSecretTextEffect)newEffect { if (newEffect == effect) return; effect = newEffect; [self setNeedsDisplay]; } - (CGFloat) heightForLines:(NSInteger)lines givenWidth:(CGFloat)width { CGFloat y = 0.0; NSInteger offset = 0; CGRect frameCopy = self.frame; self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, width, self.frame.size.height); NSInteger currentLine = 0; while (offset != [text length] && (currentLine < lines || lines == -1)) { NSInteger nextOffset = [self nextWrapOffsetForGlyphs:offset]; offset = nextOffset; y += textHeight*self.lineSpacing; currentLine++; } if (y == 0) y = textHeight*self.lineSpacing; self.frame = frameCopy; return y+self.padding*2; } - (void) drawRect:(CGRect)rect { //fill rect with red CGContextRef context = UIGraphicsGetCurrentContext(); if (debug) { CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor); CGContextFillRect(context, rect); } if (useSSFont) { if (glyphs) { CGAffineTransform fontTransform = CGAffineTransformMake(1.0, 0.0, 0.0, -1.0, 0.0, 0.0); CGContextSetTextMatrix(context, fontTransform); CGContextSetFont(context, ssfont.font); CGContextSetFontSize(context, ssfont.size); CGContextSetTextDrawingMode(context, kCGTextFill); NSInteger offset = 0; CGFloat y = 0.0; while (offset != [text length]) { NSInteger nextOffset = [self nextWrapOffsetForGlyphs:offset]; //strip out drawing spaces and newline characters... NSInteger notBlankOffset = nextOffset; if (notBlankOffset < [text length]) { while ([[NSCharacterSet whitespaceAndNewlineCharacterSet] characterIsMember:[text characterAtIndex:notBlankOffset-1]] && notBlankOffset > offset) { notBlankOffset--; } } CGFloat x = padding; if (align == SemiSecretTextAlignCenter) { x = floor((self.frame.size.width-lineWidth)/2); } else if (align == SemiSecretTextAlignRight) { x = self.frame.size.width-lineWidth-padding; } //offset:notBlankOffset-offset switch (effect) { case SemiSecretTextEffectEngravedShadow: { CGContextSetFillColorWithColor(context, shadowColor); CGContextShowGlyphsAtPoint(context, x, padding+textHeight-1.0+y, &glyphs[offset], notBlankOffset-offset); break; } case SemiSecretTextEffectReverseEngravedShadow: { CGContextSetFillColorWithColor(context, shadowColor); CGContextShowGlyphsAtPoint(context, x, padding+textHeight+1.0+y, &glyphs[offset], notBlankOffset-offset); break; } case SemiSecretTextEffectBlurredShadow: { CGContextSetShadowWithColor(context, CGSizeMake(0,0), 5.0, shadowColor); break; } } CGContextSetFillColorWithColor(context, color); if (y + textHeight*lineSpacing < self.bounds.size.height || !wrapped) { //don't draw CGContextShowGlyphsAtPoint(context, x, padding+textHeight+y, &glyphs[offset], notBlankOffset-offset); } offset = nextOffset; y += textHeight*lineSpacing; } } } if (useUIFont && uifont) { switch (effect) { case SemiSecretTextEffectEngravedShadow: { CGContextSetFillColorWithColor(context, shadowColor); [text drawAtPoint:CGPointMake(10.0,10.0-1.0) withFont:uifont]; break; } case SemiSecretTextEffectReverseEngravedShadow: { CGContextSetFillColorWithColor(context, shadowColor); [text drawAtPoint:CGPointMake(10.0,10.0+1.0) withFont:uifont]; break; } case SemiSecretTextEffectBlurredShadow: { CGContextSetShadowWithColor(context, CGSizeMake(0,0), 5.0, shadowColor); break; } } CGContextSetFillColorWithColor(context, color); [text drawAtPoint:CGPointMake(10.0,10.0) withFont:uifont]; } } - (NSInteger) nextWrapOffsetForGlyphs:(NSInteger)offset { NSInteger i; CGFloat unitsPerEm = CGFontGetUnitsPerEm(ssfont.font); int * advances = malloc(sizeof(int)*glyphLength); CGFontGetGlyphAdvances(ssfont.font, glyphs, glyphLength, advances); if (offset == [text length]) { lineWidth = 0; free(advances); return NSNotFound; } if (!wrapped) { lineWidth = 0; for (i=offset; i<[text length]; ++i) lineWidth += advances[i]*ssfont.size/unitsPerEm; free(advances); return [text length]; } //search for new line NSRange newlineRange = [text rangeOfCharacterFromSet:[NSCharacterSet newlineCharacterSet] options:0 range:NSMakeRange(offset, [text length]-offset)]; CGFloat width = self.bounds.size.width; CGFloat offsetWidth = 0.0; NSInteger nextOffset = offset; for (nextOffset=offset; nextOffset width-self.padding*2) { break; } } if (newlineRange.location != NSNotFound && newlineRange.location <= nextOffset) { nextOffset = newlineRange.location+1; lineWidth = 0; for (i=offset; i