diff --git a/AppKit/CPTextView/CPParagraphStyle.j b/AppKit/CPTextView/CPParagraphStyle.j index 54d88ee73..c6d2c1b71 100644 --- a/AppKit/CPTextView/CPParagraphStyle.j +++ b/AppKit/CPTextView/CPParagraphStyle.j @@ -2,10 +2,6 @@ * CPParagraphStyle.j * AppKit * - * FIXME - * This is basically a stub. - * We need to store all the spacing informations as well as writing direction (among others) - * * Created by Daniel Boehringer on 11/01/2014 * Copyright Daniel Boehringer 2014. * @@ -26,198 +22,303 @@ @import @import +@import -@import "CPText.j" +var CPParagraphStyleAttributeName = @"CPParagraphStyleAttributeName"; -CPLeftTabStopType = 0; +// Standard Tab Interval (28pts is roughly 4 spaces in standard fonts) +var kDefaultTabInterval = 28.0; -CPParagraphStyleAttributeName = @"CPParagraphStyleAttributeName"; +// MARK: - CPTextTab Implementation -var _sharedDefaultParagraphStyle, - _defaultTabStopArray; - -@implementation CPParagraphStyle : CPObject +@implementation CPTextTab : CPObject { - CPArray _tabStops @accessors(property=tabStops); - CPTextAlignment _alignment @accessors(property=alignment); - unsigned _firstLineHeadIndent @accessors(property=firstLineHeadIndent); - unsigned _headIndent @accessors(property=headIndent); - unsigned _tailIndent @accessors(property=tailIndent); - unsigned _paragraphSpacing @accessors(property=paragraphSpacing); - unsigned _minimumLineHeight @accessors(property=minimumLineHeight); - unsigned _maximumLineHeight @accessors(property=maximumLineHeight); - unsigned _lineSpacing @accessors(property=lineSpacing); + CPTextAlignment _alignment @accessors(readonly, property=alignment); + float _location @accessors(readonly, property=location); + CPDictionary _options @accessors(readonly, property=options); } - -#pragma mark - -#pragma mark Class methods - -+ (CPParagraphStyle)defaultParagraphStyle +- (id)initWithTextAlignment:(CPTextAlignment)anAlignment location:(float)aLocation options:(CPDictionary)options { - if (!_sharedDefaultParagraphStyle) - _sharedDefaultParagraphStyle = [self new]; - - return _sharedDefaultParagraphStyle; -} - -+ (CPArray)_defaultTabStops -{ - if (!_defaultTabStopArray) + if (self = [super init]) { - var i; - _defaultTabStopArray = []; - - // FIXME: Define constants for these magic numbers: 13, 28 - for (i = 1; i < 16 ; i++) - { - _defaultTabStopArray.push([[CPTextTab alloc] initWithType:CPLeftTabStopType location:i * 28]); - } + _alignment = anAlignment; + _location = aLocation; + _options = [options copy]; } - - return _defaultTabStopArray; -} - - -#pragma mark - -#pragma mark Init methods - -- (id)init -{ - [self _initWithDefaults]; - return self; } -- (CPParagraphStyle)initWithParagraphStyle:(CPParagraphStyle)other +// Convenience initializer matching AppKit behavior +- (id)initWithType:(CPTabStopType)aType location:(float)aLocation { - self = [super init]; - - _tabStops = [other._tabStops copy]; - _alignment = other._alignment; - _firstLineHeadIndent = other._firstLineHeadIndent; - _headIndent = other._headIndent; - _tailIndent = other._tailIndent; - _paragraphSpacing = other._paragraphSpacing; - _minimumLineHeight = other._minimumLineHeight; - _maximumLineHeight = other._maximumLineHeight; - _lineSpacing = other._lineSpacing; - - return self; + // Map old TabStopType to TextAlignment for modern compatibility + return [self initWithTextAlignment:aType location:aLocation options:nil]; } -- (void)_initWithDefaults +- (BOOL)isEqual:(id)other { - _alignment = CPLeftTextAlignment; - _tabStops = [[[self class] _defaultTabStops] copy]; -} + if (self === other) return YES; + if (![other isKindOfClass:[CPTextTab class]]) return NO; -- (void)addTabStop:(CPTextTab)aStop -{ - _tabStops.push(aStop); + return _location === [other location] && + _alignment === [other alignment] && + ((_options == nil && [other options] == nil) || [_options isEqualToDictionary:[other options]]); } - (id)copy { - var other = [[self class] alloc]; + return [[CPTextTab alloc] initWithTextAlignment:_alignment location:_location options:_options]; +} - return [other initWithParagraphStyle:self]; +- (id)initWithCoder:(CPCoder)aCoder +{ + if (self = [super init]) + { + _alignment = [aCoder decodeIntForKey:@"CPTextTabAlignment"]; + _location = [aCoder decodeFloatForKey:@"CPTextTabLocation"]; + _options = [aCoder decodeObjectForKey:@"CPTextTabOptions"]; + } + return self; +} + +- (void)encodeWithCoder:(CPCoder)aCoder +{ + [aCoder encodeInt:_alignment forKey:@"CPTextTabAlignment"]; + [aCoder encodeFloat:_location forKey:@"CPTextTabLocation"]; + [aCoder encodeObject:_options forKey:@"CPTextTabOptions"]; } @end -var CPParagraphStyleTabStopsKey = @"CPParagraphStyleTabStopsKey", - CPParagraphStyleAlignmentKey = @"CPParagraphStyleAlignmentKey", - CPParagraphStyleFirstLineHeadIndentKey = @"CPParagraphStyleFirstLineHeadIndentKey", - CPParagraphStyleHeadIndentKey = @"CPParagraphStyleHeadIndentKey", - CPParagraphStyleTailIndentKey = @"CPParagraphStyleTailIndentKey", - CPParagraphStyleParagraphSpacingKey = @"CPParagraphStyleParagraphSpacingKey", - CPParagraphStyleMinimumLineHeightKey = @"CPParagraphStyleMinimumLineHeightKey", - CPParagraphStyleMaximumLineHeightKey = @"CPParagraphStyleMaximumLineHeightKey", - CPParagraphStyleLineSpacingKey = @"CPParagraphStyleLineSpacingKey"; +// MARK: - CPParagraphStyle Implementation + +var _sharedDefaultParagraphStyle = nil; + +@implementation CPParagraphStyle : CPObject +{ + float _lineSpacing @accessors(readonly, property=lineSpacing); + float _paragraphSpacing @accessors(readonly, property=paragraphSpacing); + CPTextAlignment _alignment @accessors(readonly, property=alignment); + float _headIndent @accessors(readonly, property=headIndent); + float _tailIndent @accessors(readonly, property=tailIndent); + float _firstLineHeadIndent @accessors(readonly, property=firstLineHeadIndent); + float _minimumLineHeight @accessors(readonly, property=minimumLineHeight); + float _maximumLineHeight @accessors(readonly, property=maximumLineHeight); + CPLineBreakMode _lineBreakMode @accessors(readonly, property=lineBreakMode); + CPWritingDirection _baseWritingDirection @accessors(readonly, property=baseWritingDirection); + float _lineHeightMultiple @accessors(readonly, property=lineHeightMultiple); + float _paragraphSpacingBefore @accessors(readonly, property=paragraphSpacingBefore); + float _defaultTabInterval @accessors(readonly, property=defaultTabInterval); + CPArray _tabStops @accessors(readonly, property=tabStops); +} + ++ (CPParagraphStyle)defaultParagraphStyle +{ + if (!_sharedDefaultParagraphStyle) + { + _sharedDefaultParagraphStyle = [[CPParagraphStyle alloc] init]; + // Ensure defaults are set on the shared instance internal vars + // Since it's immutable, we rely on the init to set these. + } + return _sharedDefaultParagraphStyle; +} + ++ (CPWritingDirection)defaultWritingDirectionForLanguage:(CPString)languageName +{ + // Simplified: Cappuccino usually assumes LTR unless specified otherwise. + return CPWritingDirectionLeftToRight; +} + +- (id)init +{ + if (self = [super init]) + { + _lineSpacing = 0.0; + _paragraphSpacing = 0.0; + _alignment = CPLeftTextAlignment; + _headIndent = 0.0; + _tailIndent = 0.0; + _firstLineHeadIndent = 0.0; + _minimumLineHeight = 0.0; + _maximumLineHeight = 0.0; + _lineBreakMode = CPLineBreakByWordWrapping; + _baseWritingDirection = CPWritingDirectionNatural; + _lineHeightMultiple = 0.0; + _paragraphSpacingBefore = 0.0; + _defaultTabInterval = kDefaultTabInterval; + + // Generate default tab stops + _tabStops = []; + for (var i = 1; i <= 12; i++) + { + [_tabStops addObject:[[CPTextTab alloc] initWithType:CPLeftTextAlignment + location:i * kDefaultTabInterval]]; + } + } + return self; +} + +- (id)initWithParagraphStyle:(CPParagraphStyle)other +{ + if (self = [super init]) + { + _lineSpacing = [other lineSpacing]; + _paragraphSpacing = [other paragraphSpacing]; + _alignment = [other alignment]; + _headIndent = [other headIndent]; + _tailIndent = [other tailIndent]; + _firstLineHeadIndent = [other firstLineHeadIndent]; + _minimumLineHeight = [other minimumLineHeight]; + _maximumLineHeight = [other maximumLineHeight]; + _lineBreakMode = [other lineBreakMode]; + _baseWritingDirection = [other baseWritingDirection]; + _lineHeightMultiple = [other lineHeightMultiple]; + _paragraphSpacingBefore = [other paragraphSpacingBefore]; + _defaultTabInterval = [other defaultTabInterval]; + _tabStops = [[other tabStops] copy]; + } + return self; +} + +- (id)copy +{ + // Since this class is immutable, return self. + // Subclasses (Mutable) will override. + if ([self class] === [CPParagraphStyle class]) + return self; + + return [[CPParagraphStyle alloc] initWithParagraphStyle:self]; +} + +- (id)mutableCopy +{ + return [[CPMutableParagraphStyle alloc] initWithParagraphStyle:self]; +} + +// MARK: - Equality + +- (BOOL)isEqual:(id)other +{ + if (self === other) return YES; + if (![other isKindOfClass:[CPParagraphStyle class]]) return NO; + + return _lineSpacing === [other lineSpacing] && + _paragraphSpacing === [other paragraphSpacing] && + _alignment === [other alignment] && + _headIndent === [other headIndent] && + _tailIndent === [other tailIndent] && + _firstLineHeadIndent === [other firstLineHeadIndent] && + _lineBreakMode === [other lineBreakMode] && + [_tabStops isEqualToArray:[other tabStops]]; +} + +@end + + +// MARK: - CPMutableParagraphStyle Implementation + +@implementation CPMutableParagraphStyle : CPParagraphStyle +{ +} + + +- (void)addTabStop:(CPTextTab)aTabStop +{ + // Copy on write logic would go here if we shared structure, + // but here we just mutate the array. + [_tabStops addObject:aTabStop]; +} + +- (void)setTabStops:(CPArray)newTabStops +{ + if (_tabStops === newTabStops) return; + _tabStops = [newTabStops copy]; +} + +- (id)copyWithZone:(CPZone)aZone +{ + // Return an immutable copy + return [[CPParagraphStyle alloc] initWithParagraphStyle:self]; +} + +@end + + +// MARK: - CPCoding + +var CPParagraphStyleLineSpacingKey = @"CPParagraphStyleLineSpacingKey", + CPParagraphStyleParagraphSpacingKey = @"CPParagraphStyleParagraphSpacingKey", + CPParagraphStyleAlignmentKey = @"CPParagraphStyleAlignmentKey", + CPParagraphStyleHeadIndentKey = @"CPParagraphStyleHeadIndentKey", + CPParagraphStyleTailIndentKey = @"CPParagraphStyleTailIndentKey", + CPParagraphStyleFirstLineHeadIndentKey = @"CPParagraphStyleFirstLineHeadIndentKey", + CPParagraphStyleMinimumLineHeightKey = @"CPParagraphStyleMinimumLineHeightKey", + CPParagraphStyleMaximumLineHeightKey = @"CPParagraphStyleMaximumLineHeightKey", + CPParagraphStyleLineBreakModeKey = @"CPParagraphStyleLineBreakModeKey", + CPParagraphStyleTabStopsKey = @"CPParagraphStyleTabStopsKey", + CPParagraphStyleBaseWritingDirectionKey = @"CPParagraphStyleBaseWritingDirectionKey", + CPParagraphStyleLineHeightMultipleKey = @"CPParagraphStyleLineHeightMultipleKey", + CPParagraphStyleParagraphSpacingBeforeKey = @"CPParagraphStyleParagraphSpacingBeforeKey", + CPParagraphStyleDefaultTabIntervalKey = @"CPParagraphStyleDefaultTabIntervalKey"; @implementation CPParagraphStyle (CPCoding) -- (id)initWithCoder:(id)aCoder +- (id)initWithCoder:(CPCoder)aCoder { - self = [self init]; - - if (self) + if (self = [super init]) { - _tabStops = [aCoder decodeObjectForKey:"CPParagraphStyleTabStopsKey"]; - _alignment = [aCoder decodeIntForKey:"CPParagraphStyleAlignmentKey"]; - _firstLineHeadIndent = [aCoder decodeIntForKey:"CPParagraphStyleFirstLineHeadIndentKey"]; - _headIndent = [aCoder decodeIntForKey:"CPParagraphStyleHeadIndentKey"]; - _tailIndent = [aCoder decodeIntForKey:"CPParagraphStyleTailIndentKey"]; - _paragraphSpacing = [aCoder decodeIntForKey:"CPParagraphStyleParagraphSpacingKey"]; - _minimumLineHeight = [aCoder decodeIntForKey:"CPParagraphStyleMinimumLineHeightKey"]; - _maximumLineHeight = [aCoder decodeIntForKey:"CPParagraphStyleMaximumLineHeightKey"]; - _lineSpacing = [aCoder decodeIntForKey:"CPParagraphStyleLineSpacingKey"]; + _lineSpacing = [aCoder decodeFloatForKey:CPParagraphStyleLineSpacingKey]; + _paragraphSpacing = [aCoder decodeFloatForKey:CPParagraphStyleParagraphSpacingKey]; + _alignment = [aCoder decodeIntForKey:CPParagraphStyleAlignmentKey]; + _headIndent = [aCoder decodeFloatForKey:CPParagraphStyleHeadIndentKey]; + _tailIndent = [aCoder decodeFloatForKey:CPParagraphStyleTailIndentKey]; + _firstLineHeadIndent = [aCoder decodeFloatForKey:CPParagraphStyleFirstLineHeadIndentKey]; + _minimumLineHeight = [aCoder decodeFloatForKey:CPParagraphStyleMinimumLineHeightKey]; + _maximumLineHeight = [aCoder decodeFloatForKey:CPParagraphStyleMaximumLineHeightKey]; + _lineBreakMode = [aCoder decodeIntForKey:CPParagraphStyleLineBreakModeKey]; + _tabStops = [aCoder decodeObjectForKey:CPParagraphStyleTabStopsKey]; + + // Handle potential missing keys for backward compatibility or new properties + if ([aCoder containsValueForKey:CPParagraphStyleBaseWritingDirectionKey]) + _baseWritingDirection = [aCoder decodeIntForKey:CPParagraphStyleBaseWritingDirectionKey]; + else + _baseWritingDirection = CPWritingDirectionNatural; + + if ([aCoder containsValueForKey:CPParagraphStyleLineHeightMultipleKey]) + _lineHeightMultiple = [aCoder decodeFloatForKey:CPParagraphStyleLineHeightMultipleKey]; + + if ([aCoder containsValueForKey:CPParagraphStyleParagraphSpacingBeforeKey]) + _paragraphSpacingBefore = [aCoder decodeFloatForKey:CPParagraphStyleParagraphSpacingBeforeKey]; + + if ([aCoder containsValueForKey:CPParagraphStyleDefaultTabIntervalKey]) + _defaultTabInterval = [aCoder decodeFloatForKey:CPParagraphStyleDefaultTabIntervalKey]; + else + _defaultTabInterval = kDefaultTabInterval; + + if (!_tabStops) _tabStops = []; } - return self; } -- (void)encodeWithCoder:(id)aCoder +- (void)encodeWithCoder:(CPCoder)aCoder { - [aCoder encodeInt:_alignment forKey:"CPParagraphStyleAlignmentKey"]; - [aCoder encodeObject:_tabStops forKey:"CPParagraphStyleTabStopsKey"]; - [aCoder encodeInt:_firstLineHeadIndent forKey:"CPParagraphStyleFirstLineHeadIndentKey"]; - [aCoder encodeInt:_headIndent forKey:"CPParagraphStyleHeadIndentKey"]; - [aCoder encodeInt:_tailIndent forKey:"CPParagraphStyleTailIndentKey"]; - [aCoder encodeInt:_paragraphSpacing forKey:"CPParagraphStyleParagraphSpacingKey"]; - [aCoder encodeInt:_minimumLineHeight forKey:"CPParagraphStyleMinimumLineHeightKey"]; - [aCoder encodeInt:_maximumLineHeight forKey:"CPParagraphStyleMaximumLineHeightKey"]; - [aCoder encodeInt:_lineSpacing forKey:"CPParagraphStyleLineSpacingKey"]; -} - - -@end - - -@implementation CPTextTab : CPObject -{ - int _type @accessors(property = tabStopType); - double _location @accessors(property = location); -} - -- (id)initWithType:(CPTabStopType) aType location:(double) aLocation -{ - if ([self = [super init]]) - { - _type = aType; - _location = aLocation; - } - - return self; -} - -@end - - -var CPTextTabTypeKey = @"CPTextTabTypeKey", - CPTextTabLocationKey = @"CPTextTabLocationKey"; - -@implementation CPTextTab (CPCoding) - -- (id)initWithCoder:(id)aCoder -{ - self = [self init]; - - if (self) - { - _type = [aCoder decodeIntForKey:"CPTextTabTypeKey"]; - _location = [aCoder decodeDoubleForKey:"CPTextTabLocationKey"]; - } - - return self; -} - -- (void)encodeWithCoder:(id)aCoder -{ - [aCoder encodeInt:_type forKey:"CPTextTabTypeKey"]; - [aCoder encodeDouble:_location forKey:"CPTextTabLocationKey"]; + [aCoder encodeFloat:_lineSpacing forKey:CPParagraphStyleLineSpacingKey]; + [aCoder encodeFloat:_paragraphSpacing forKey:CPParagraphStyleParagraphSpacingKey]; + [aCoder encodeInt:_alignment forKey:CPParagraphStyleAlignmentKey]; + [aCoder encodeFloat:_headIndent forKey:CPParagraphStyleHeadIndentKey]; + [aCoder encodeFloat:_tailIndent forKey:CPParagraphStyleTailIndentKey]; + [aCoder encodeFloat:_firstLineHeadIndent forKey:CPParagraphStyleFirstLineHeadIndentKey]; + [aCoder encodeFloat:_minimumLineHeight forKey:CPParagraphStyleMinimumLineHeightKey]; + [aCoder encodeFloat:_maximumLineHeight forKey:CPParagraphStyleMaximumLineHeightKey]; + [aCoder encodeInt:_lineBreakMode forKey:CPParagraphStyleLineBreakModeKey]; + [aCoder encodeObject:_tabStops forKey:CPParagraphStyleTabStopsKey]; + + [aCoder encodeInt:_baseWritingDirection forKey:CPParagraphStyleBaseWritingDirectionKey]; + [aCoder encodeFloat:_lineHeightMultiple forKey:CPParagraphStyleLineHeightMultipleKey]; + [aCoder encodeFloat:_paragraphSpacingBefore forKey:CPParagraphStyleParagraphSpacingBeforeKey]; + [aCoder encodeFloat:_defaultTabInterval forKey:CPParagraphStyleDefaultTabIntervalKey]; } @end