CPTextView commit

This commit is contained in:
daboe01
2014-02-08 20:43:15 +01:00
parent 1814f6b545
commit 9edd8d6f66
13 changed files with 6769 additions and 2 deletions
+40
View File
@@ -24,6 +24,7 @@
@import <Foundation/CPBundle.j>
@import "CPView.j"
@import "CPFontDescriptor.j"
CPFontDefaultSystemFontFace = @"Arial, sans-serif";
CPFontDefaultSystemFontSize = 12;
@@ -433,6 +434,45 @@ following:
@end
@implementation CPFont(DescriptorAdditions)
- (id)_initWithFontDescriptor:(CPFontDescriptor)fontDescriptor
{
var aName = [fontDescriptor objectForKey: CPFontNameAttribute] ,
aSize = [fontDescriptor pointSize],
isBold = [fontDescriptor symbolicTraits] & CPFontBoldTrait,
isItalic = [fontDescriptor symbolicTraits] & CPFontItalicTrait;
return [self _initWithName:aName size:aSize bold:isBold italic:isItalic system:NO];
}
+ (CPFont)fontWithDescriptor:(CPFontDescriptor)fontDescriptor size:(float)aSize
{
var aName = [fontDescriptor objectForKey: CPFontNameAttribute],
isBold = [fontDescriptor symbolicTraits] & CPFontBoldTrait,
isItalic = [fontDescriptor symbolicTraits] & CPFontItalicTrait;
return [self _fontWithName:aName size:aSize || [fontDescriptor pointSize] bold:isBold italic:isItalic];
}
- (CPFontDescriptor)fontDescriptor
{
var traits = 0;
if ([self isBold])
traits |= CPFontBoldTrait;
if ([self isItalic])
traits |= CPFontItalicTrait;
var descriptor = [[CPFontDescriptor fontDescriptorWithName:_name size:_size] fontDescriptorWithSymbolicTraits:traits];
return descriptor;
}
@end
var CPFontNameKey = @"CPFontNameKey",
CPFontSizeKey = @"CPFontSizeKey",
CPFontIsBoldKey = @"CPFontIsBoldKey",
+185 -1
View File
@@ -23,6 +23,8 @@
@import <Foundation/CPObject.j>
@import "CPFont.j"
@import "CPFontPanel.j"
@import "CPFontDescriptor.j"
@global CPApp
@@ -41,7 +43,20 @@ CPUnitalicFontMask = 1 << 24;
var CPSharedFontManager = nil,
CPFontManagerFactory = Nil;
CPFontManagerFactory = Nil,
CPFontPanelFactory = Nil;
/*
modifyFont: sender's tag
*/
CPNoFontChangeAction = 0;
CPViaPanelFontAction = 1;
CPAddTraitFontAction = 2;
CPSizeUpFontAction = 3;
CPSizeDownFontAction = 4;
CPHeavierFontAction = 5;
CPLighterFontAction = 6;
CPRemoveTraitFontAction = 7;
/*!
@ingroup appkit
@@ -219,6 +234,174 @@ var CPSharedFontManager = nil,
return [CPApp sendAction:_action to:_target from:self];
}
/*!
This method open the font panel, create it if necessary.
@param sender The object that sent the message.
*/
- (CPFontPanel)fontPanel:(BOOL)createIt
{
var panel = nil,
panelExists = [CPFontPanelFactory sharedFontPanelExists];
if ((panelExists) || (!panelExists && createIt))
panel = [CPFontPanelFactory sharedFontPanel];
return panel;
}
/*!
Convert a font to have the specified Font traits. The font is unchanged expect for the specified Font traits.
Using CPUnboldFontMask or CPUnitalicFontMask will respectively remove Bold and Italic traits.
@param aFont The font to convert.
@param fontTrait The new font traits mask.
@result The converted font or \c aFont if the conversion failed.
*/
- (CPFont)convertFont:(CPFont)aFont toHaveTrait:(CPFontTraitMask)fontTrait
{
var attributes = [[[aFont fontDescriptor] fontAttributes] copy],
symbolicTrait = [[aFont fontDescriptor] symbolicTraits];
if (fontTrait & CPBoldFontMask)
symbolicTrait |= CPFontBoldTrait;
if (fontTrait & CPItalicFontMask)
symbolicTrait |= CPFontItalicTrait;
if (fontTrait & CPUnboldFontMask) /* FIXME: this only change CPFontSymbolicTrait what about CPFontWeightTrait */
symbolicTrait &= ~CPFontBoldTrait;
if (fontTrait & CPUnitalicFontMask)
symbolicTrait &= ~CPFontItalicTrait;
if (fontTrait & CPExpandedFontMask)
symbolicTrait |= CPFontExpandedTrait;
if (fontTrait & CPSmallCapsFontMask)
symbolicTrait |= CPFontSmallCapsTrait;
if (![attributes containsKey:CPFontTraitsAttribute])
[attributes setObject:[CPDictionary dictionaryWithObject:[CPNumber numberWithUnsignedInt:symbolicTrait]
forKey:CPFontSymbolicTrait] forKey:CPFontTraitsAttribute];
else
[[attributes objectForKey:CPFontTraitsAttribute] setObject:[CPNumber numberWithUnsignedInt:symbolicTrait]
forKey:CPFontSymbolicTrait];
return [[aFont class] fontWithDescriptor:[CPFontDescriptor fontDescriptorWithFontAttributes:attributes] size:0.0];
}
/*!
Convert a font to not have the specified Font traits. The font is unchanged expect for the specified Font traits.
@param aFont The font to convert.
@param fontTrait The font traits mask to remove.
@result The converted font or \c aFont if the conversion failed.
*/
- (CPFont)convertFont:(CPFont)aFont toNotHaveTrait:(CPFontTraitMask)fontTrait
{
var attributes = [[[aFont fontDescriptor] fontAttributes] copy],
symbolicTrait = [[aFont fontDescriptor] symbolicTraits];
if ((fontTrait & CPBoldFontMask) || (fontTrait & CPUnboldFontMask)) /* FIXME: see convertFont:toHaveTrait: about CPFontWeightTrait */
symbolicTrait &= ~CPFontBoldTrait;
if ((fontTrait & CPItalicFontMask) || (fontTrait & CPUnitalicFontMask))
symbolicTrait &= ~CPFontItalicTrait;
if (fontTrait & CPExpandedFontMask)
symbolicTrait &= ~CPFontExpandedTrait;
if (fontTrait & CPSmallCapsFontMask)
symbolicTrait &= ~CPFontSmallCapsTrait;
if (![attributes containsKey:CPFontTraitsAttribute])
[attributes setObject:[CPDictionary dictionaryWithObject:[CPNumber numberWithUnsignedInt:symbolicTrait] forKey:CPFontSymbolicTrait] forKey:CPFontTraitsAttribute];
else
[[attributes objectForKey:CPFontTraitsAttribute] setObject:[CPNumber numberWithUnsignedInt:symbolicTrait] forKey:CPFontSymbolicTrait];
return [[aFont class] fontWithDescriptor:[CPFontDescriptor fontDescriptorWithFontAttributes:attributes] size:0.0];
}
/*!
Convert a font to have specified size. The font is unchanged expect for the specified size.
@param aFont The font to convert.
@param aSize The new font size.
@result The converted font or \c aFont if the conversion failed.
*/
- (CPFont)convertFont:(CPFont)aFont toSize:(float)aSize
{
var descriptor = [aFont fontDescriptor];
return [[aFont class] fontWithDescriptor: descriptor size:aSize]
}
- (void)orderFrontFontPanel:(id)sender
{
[[self fontPanel:YES] orderFront:sender];
}
- (void)modifyFont:(id)sender
{
_fontAction = [sender tag];
[self sendAction];
if (_selectedFont)
[self setSelectedFont:[self convertFont:_selectedFont] isMultiple:NO];
}
/*!
This method causes the receiver to send its action message.
@param sender The object that sent the message. (a Font panel)
*/
- (void)modifyFontViaPanel:(id)sender
{
_fontAction = CPViaPanelFontAction;
if (_selectedFont)
[self setSelectedFont:[self convertFont:_selectedFont] isMultiple:NO];
[self sendAction];
}
/*!
Convert a font according to current font changes, provided by the object that initiated the font change.
@param aFont The font to convert.
@result The converted font or \c aFont if the conversion failed.
*/
- (CPFont)convertFont:(CPFont)aFont
{
var newFont = nil;
switch (_fontAction)
{
case CPNoFontChangeAction:
newFont = aFont;
break;
case CPViaPanelFontAction:
newFont = [[self fontPanel:NO] panelConvertFont:aFont];
break;
case CPAddTraitFontAction:
newFont = [self convertFont:aFont toHaveTrait:_currentFontTrait];
break;
case CPSizeUpFontAction:
newFont = [self convertFont:aFont toSize:[aFont size] + 1.0]; /* any limit ? */
break;
case CPSizeDownFontAction:
if ([aFont size] > 1)
newFont = [self convertFont:aFont toSize:[aFont size] - 1.0];
/* else CPBeep() :-p */
break;
default:
CPLog.trace(@"-[" + [self className] + " " + _cmd + "] unsupported font action: " + _fontAction + " aFont unchanged");
newFont = aFont;
break;
}
return newFont;
}
@end
var _CPFontDetectSpan,
@@ -300,3 +483,4 @@ var _CPFontDetectPickTwoDifferentFonts = function(candidates)
};
[CPFontManager setFontManagerFactory:[CPFontManager class]];
[CPFontManager setFontPanelFactory:CPFontPanel];
+218 -1
View File
@@ -5,6 +5,15 @@
* Created by Alexander Ljungberg.
* Copyright 2010, WireLoad, LLC.
*
* additions from
*
* Daniel Boehringer on 8/02/2014.
* Copyright Daniel Boehringer on 8/02/2014.
*
* Emmanuel Maillard on 28/02/2010.
* Copyright Emmanuel Maillard 2010.
*
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
@@ -20,6 +29,13 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
@import "CPView.j"
@import "RTFProducer.j"
@import "RTFParser.j"
CPParagraphSeparatorCharacter = 0x2029;
CPLineSeparatorCharacter = 0x2028;
CPEnterCharacter = "\u0003";
CPBackspaceCharacter = "\u0008";
CPTabCharacter = "\u0009";
@@ -38,4 +54,205 @@ CPLeftTextMovement = 19;
CPRightTextMovement = 20;
CPUpTextMovement = 21;
CPDownTextMovement = 22;
CPCancelTextMovement = 23;
CPCancelTextMovement = 23;
/*
CPText notifications
*/
CPTextDidBeginEditingNotification = @"CPTextDidBeginEditingNotification";
CPTextDidChangeNotification = @"CPTextDidChangeNotification";
CPTextDidEndEditingNotification = @"CPTextDidEndEditingNotification";
@implementation CPText : CPControl
{
}
- (void)changeFont:(id)sender
{
CPLog.error(@"-[CPText " + _cmd + "] subclass responsibility");
}
- (void)copy:(id)sender
{
var selectedRange = [self selectedRange];
if (selectedRange.length < 1)
return;
var pasteboard = [CPPasteboard generalPasteboard],
stringForPasting = [[self stringValue] substringWithRange:selectedRange];
[pasteboard declareTypes:[CPStringPboardType] owner:nil];
if ([self isRichText])
{
// crude hack to make rich pasting possible in chrome and firefox. this requires a RTF roundtrip, unfortunately
var richData = [RTFProducer produceRTF:[[self textStorage] attributedSubstringFromRange:selectedRange] documentAttributes: @{}];
[pasteboard setString:richData forType:CPStringPboardType];
}
else
[pasteboard setString:stringForPasting forType:CPStringPboardType];
}
- (void)paste:(id)sender
{
var pasteboard = [CPPasteboard generalPasteboard],
// dataForPasting = [pasteboard dataForType:CPRichStringPboardType],
stringForPasting = [pasteboard stringForType:CPStringPboardType];
if ([stringForPasting hasPrefix:"{\\rtf1\\ansi"])
stringForPasting = [[_RTFParser new] parseRTF:stringForPasting];
if (![self isRichText] && [stringForPasting isKindOfClass:[CPAttributedString class]])
stringForPasting = stringForPasting._string;
if (stringForPasting)
[self insertText:stringForPasting];
}
- (void)copyFont:(id)sender
{
CPLog.error(@"-[CPText " + _cmd + "] subclass responsibility");
}
- (void)cut:(id)sender
{
[self copy:sender];
var loc = [self selectedRange].location;
[self replaceCharactersInRange:[self selectedRange] withString:""];
[self setSelectedRange:CPMakeRange(loc,0) ];
}
- (void)delete:(id)sender
{
CPLog.error(@"-[CPText "+_cmd+"] subclass responsibility");
}
- (CPFont)font:(CPFont)aFont
{
CPLog.error(@"-[CPText "+_cmd+"] subclass responsibility");
return nil;
}
- (BOOL)isHorizontallyResizable
{
CPLog.error(@"-[CPText "+_cmd+"] subclass responsibility");
return NO;
}
- (BOOL)isRichText
{
CPLog.error(@"-[CPText "+_cmd+"] subclass responsibility");
return NO;
}
- (BOOL)isRulerVisible
{
CPLog.error(@"-[CPText "+_cmd+"] subclass responsibility");
return NO;
}
- (BOOL)isVerticallyResizable
{
CPLog.error(@"-[CPText "+_cmd+"] subclass responsibility");
return NO;
}
- (CPSize)maxSize
{
CPLog.error(@"-[CPText "+_cmd+"] subclass responsibility");
return CPMakeSize(0,0);
}
- (CPSize)minSize
{
CPLog.error(@"-[CPText "+_cmd+"] subclass responsibility");
return CPMakeSize(0,0);
}
- (void)pasteFont:(id)sender
{
CPLog.error(@"-[CPText "+_cmd+"] subclass responsibility");
}
- (void)replaceCharactersInRange:(CPRange)aRange withString:(CPString)aString
{
CPLog.error(@"-[CPText "+_cmd+"] subclass responsibility");
}
- (void)scrollRangeToVisible:(CPRange)aRange
{
CPLog.error(@"-[CPText "+_cmd+"] subclass responsibility");
}
- (void)selectedAll:(id)sender
{
CPLog.error(@"-[CPText "+_cmd+"] subclass responsibility");
}
- (CPRange)selectedRange
{
CPLog.error(@"-[CPText "+_cmd+"] subclass responsibility");
return CPMakeRange(CPNotFound, 0);
}
- (void)setFont:(CPFont)aFont
{
CPLog.error(@"-[CPText "+_cmd+"] subclass responsibility");
}
- (void)setFont:(CPFont)aFont rang:(CPRange)aRange
{
CPLog.error(@"-[CPText "+_cmd+"] subclass responsibility");
}
- (void)setHorizontallyResizable:(BOOL)flag
{
CPLog.error(@"-[CPText "+_cmd+"] subclass responsibility");
}
- (void)setMaxSize:(CPSize)aSize
{
CPLog.error(@"-[CPText "+_cmd+"] subclass responsibility");
}
- (void)setMinSize:(CPSize)aSize
{
CPLog.error(@"-[CPText "+_cmd+"] subclass responsibility");
}
- (void)setString:(CPString)aString
{
[self replaceCharactersInRange: CPMakeRange(0, [[self string] length]) withString:aString];
}
- (void)setUsesFontPanel:(BOOL)flag
{
CPLog.error(@"-[CPText "+_cmd+"] subclass responsibility");
}
- (void)setVerticallyResizable:(BOOL)flag
{
CPLog.error(@"-[CPText "+_cmd+"] subclass responsibility");
}
- (CPString)string
{
CPLog.error(@"-[CPText "+_cmd+"] subclass responsibility");
return nil;
}
- (void)underline:(id)sender
{
CPLog.error(@"-[CPText "+_cmd+"] subclass responsibility");
}
- (BOOL)usesFontPanel
{
CPLog.error(@"-[CPText "+_cmd+"] subclass responsibility");
return NO;
}
@end
+328
View File
@@ -0,0 +1,328 @@
/*
* CPFontDescriptor.j
* AppKit
*
* Created by Emmanuel Maillard on 07/03/10.
* Copyright Emmanuel Maillard 2010.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
@import <Foundation/CPObject.j>
/*
Font descriptor dictionary keys
*/
/*
CPFontNameAttribute contains a CPString that specified the font name
(may be an name list like: 'Marker Felt, Lucida Grande, Helvetica')
*/
CPFontNameAttribute = @"CPFontNameAttribute";
/*
CPFontSizeAttribute contains a CPString that specified the font size
(as a float value)
*/
CPFontSizeAttribute = @"CPFontSizeAttribute";
/*
CPFontTraitsAttribute a CPDictionary that contains font traits keys
(CPFontSymbolicTrait or CPFontWeightTrait)
*/
CPFontTraitsAttribute = @"CPFontTraitsAttribute";
// Font traits dictionary keys
/*
CPFontSymbolicTrait a CPNumber that contains CPFontFamilyClass and
typeface information flags.
*/
CPFontSymbolicTrait = @"CPFontSymbolicTrait";
/*
CPFontWeightTrait
We use CPString with CSS string values for font weight
(normal | bold | bolder | lighter | 100 | 200 | 300 | 400
| 500 | 600 | 700 | 800 | 900)
NOTE: Cocoa compatibility issue: NSFontWeightTrait are NSNumber for
font weight (from -1.0 to 1.0, 0.0 for normal weight).
*/
CPFontWeightTrait = @"CPFontWeightTrait";
/*
CPFontFamilyClass
*/
CPFontUnknownClass = (0 << 28);
CPFontOldStyleSerifsClass = (1 << 28);
CPFontTransitionalSerifsClass = (2 << 28);
CPFontModernSerifsClass = (3 << 28);
CPFontClarendonSerifsClass = (4 << 28);
CPFontSlabSerifsClass = (5 << 28);
CPFontFreeformSerifsClass = (7 << 28);
CPFontSansSerifClass = (8 << 28);
CPFontSerifClass = (CPFontOldStyleSerifsClass | CPFontTransitionalSerifsClass |
CPFontModernSerifsClass | CPFontClarendonSerifsClass |
CPFontSlabSerifsClass | CPFontFreeformSerifsClass);
CPFontFamilyClassMask = 0xF0000000;
/*
Typeface information
*/
CPFontItalicTrait = (1 << 0);
CPFontBoldTrait = (1 << 1);
CPFontExpandedTrait = (1 << 5); /* TODO: CCS 3 font-stretch */
CPFontCondensedTrait = (1 << 6);
CPFontSmallCapsTrait = (1 << 7);
/*!
@ingroup appkit
@class CPFontDescriptor
*/
@implementation CPFontDescriptor : CPObject
{
CPDictionary _attributes;
}
/*!
Returns a font descriptor with the specified attributes.
@param attributes a dictionary that describe the desired font descriptor
@return the requested font descriptor
*/
+ (CPFontDescriptor)fontDescriptorWithFontAttributes:(CPDictionary)attributes
{
return [[CPFontDescriptor alloc] initWithFontAttributes:attributes];
}
/*!
Returns a font descriptor with the specified name and size.
@param fontName the name of the font
@param aSize the size of the font (in points)
@return the requested font descriptor
*/
+ (CPFontDescriptor)fontDescriptorWithName:(CPString)fontName size:(float)size
{
return [[CPFontDescriptor alloc] initWithFontAttributes:[CPDictionary dictionaryWithObjects:[fontName, [CPString stringWithString:size + '']] forKeys:[CPFontNameAttribute,CPFontSizeAttribute]]];
}
/*!
Initialize a font descriptor with the specified attributes.
@param attributes a dictionary that describe the desired font descriptor
@return the requested font descriptor
*/
- (id)initWithFontAttributes:(CPDictionary)attributes
{
self = [super init];
if (self)
{
_attributes = [[CPMutableDictionary alloc] init];
if (attributes)
[_attributes addEntriesFromDictionary:attributes];
}
return self;
}
/*!
Returns a new font descriptor that is the same as the receiver but with the
specified attributes taking precedence over the existing ones.
@param attributes a dictionary that describe the desired font descriptor
@return the new font descriptor
*/
- (CPFontDescriptor)fontDescriptorByAddingAttributes:(CPDictionary)attributes
{
var attrib = [_attributes copy];
[attrib addEntriesFromDictionary:attributes];
return [[CPFontDescriptor alloc] initWithFontAttributes:attrib];
}
/*!
Returns a new font descriptor that is the same as the receiver but with the specified size taking precedence over the existing ones.
@param aSize the new size
@return the new font descriptor
*/
- (CPFontDescriptor)fontDescriptorWithSize:(float)aSize
{
var attrib = [_attributes copy];
[attrib setObject:[CPString stringWithString:aSize + ''] forKey:CPFontSizeAttribute];
return [[CPFontDescriptor alloc] initWithFontAttributes:attrib];
}
/*!
Returns a new font descriptor that is the same as the receiver but with
the specified symbolic traits taking precedence over the existing ones.
@param symbolicTraits the desired new symbolic traits
@return the new font descriptor
*/
- (CPFontDescriptor)fontDescriptorWithSymbolicTraits:(CPFontSymbolicTraits)symbolicTraits
{
var attrib = [_attributes copy];
if ([attrib objectForKey:CPFontTraitsAttribute])
[[attrib objectForKey:CPFontTraitsAttribute] setObject:[CPNumber numberWithUnsignedInt:symbolicTraits]
forKey:CPFontSymbolicTrait];
else
[attrib setObject:[CPDictionary dictionaryWithObject:[CPNumber numberWithUnsignedInt:symbolicTraits]
forKey:CPFontSymbolicTrait] forKey:CPFontTraitsAttribute];
return [[CPFontDescriptor alloc] initWithFontAttributes:attrib];
}
- (id)objectForKey:(id)aKey
{
return [_attributes objectForKey:aKey];
}
- (CPDictionary)fontAttributes
{
return _attributes;
}
- (float)pointSize
{
var value = [_attributes objectForKey:CPFontSizeAttribute];
if (value)
return [value floatValue];
return 0.0;
}
- (CPFontSymbolicTraits)symbolicTraits
{
var traits = [_attributes objectForKey:CPFontTraitsAttribute];
if (traits && [traits objectForKey:CPFontSymbolicTrait])
return [[traits objectForKey:CPFontSymbolicTrait] unsignedIntValue];
return 0;
}
@end
var CPFontDescriptorAttributesKey = @"CPFontDescriptorAttributesKey";
@implementation CPFontDescriptor (CPCoding)
/*!
Initializes the font descriptor from a coder.
@param aCoder the coder from which to read the font descriptor data
@return the initialized font
*/
- (id)initWithCoder:(CPCoder)aCoder
{
return [self initWithFontAttributes:[aCoder decodeObjectForKey:CPFontDescriptorAttributesKey]];
}
/*!
Writes the font descriptor to a coder.
@param aCoder the coder to which the data will be written
*/
- (void)encodeWithCoder:(CPCoder)aCoder
{
[aCoder encodeObject:_attributes forKey:CPFontDescriptorAttributesKey];
}
@end
var _wrapNameRegEx = new RegExp(/(\w+\s+\w+)(,*)/g);
/*
Helper methods to CPFont for generating CSS font style
*/
@implementation CPFontDescriptor (CPFontCSSHelper)
- (CPString)fontStyleCSSString
{
if ([self symbolicTraits] & CPFontItalicTrait)
return @"italic";
return @"normal";
}
- (CPString)fontWeightCSSString
{
var traitsAttributes = [_attributes objectForKey:CPFontTraitsAttribute];
if (traitsAttributes)
{
/* give preference to CPFontWeightTrait */
if ([traitsAttributes objectForKey:CPFontWeightTrait])
return [traitsAttributes objectForKey:CPFontWeightTrait];
/* else fallback to facetype symbolic traits */
if ([self symbolicTraits] & CPFontBoldTrait)
return @"bold";
}
return @"normal";
}
- (CPString)fontSizeCSSString
{
if ([_attributes objectForKey:CPFontSizeAttribute])
return [[_attributes objectForKey:CPFontSizeAttribute] intValue] + "px";
return @"";
}
- (CPString)fontFamilyCSSString
{
var aName = @"";
if ([_attributes objectForKey:CPFontNameAttribute])
aName += [_attributes objectForKey:CPFontNameAttribute].replace(_wrapNameRegEx, '"$1"$2');
var symbolicTraits = [self symbolicTraits];
if (symbolicTraits)
{
if ((symbolicTraits & CPFontFamilyClassMask) & CPFontSansSerifClass)
aName += @", sans-serif";
else if ((symbolicTraits & CPFontFamilyClassMask) & CPFontSerifClass)
aName += @", serif";
}
return aName;
}
- (CPString)fontVariantCSSString
{
if ([self symbolicTraits] & CPFontSmallCapsTrait)
return @"small-caps";
return @"normal";
}
- (CPString)cssString
{
return [CPString stringWithString:[self fontStyleCSSString] + " "
+ [self fontVariantCSSString] + " "
+ [self fontWeightCSSString] + " "
+ [self fontSizeCSSString] + " "
+ [self fontFamilyCSSString]];
}
@end
+487
View File
@@ -0,0 +1,487 @@
/*
* CPFontPanel.j
* AppKit
*
* TODOs:
* 1. make browser-width for size smaller and fix columns
* 2. sampleview is currently not shown
* 3. add all the missing features from the MacOS X counterpart
*
*
* Created by Daniel Boehringer on 2/JAN/2014.
* All modifications copyright Daniel Boehringer 2013.
* Based on original work by
* Created by Emmanuel Maillard on 06/03/2010.
* Copyright Emmanuel Maillard 2010.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
@import "CPFontManager.j"
@import "CPPanel.j"
@import "CPLayoutManager.j"
/*
Collection indexes
*/
var kTypefaceIndex_Normal = 0,
kTypefaceIndex_Italic = 1,
kTypefaceIndex_Bold = 2,
kTypefaceIndex_BoldItalic = 3;
var kToolbarHeight = 32,
kBorderSpacing = 6,
kInnerSpacing = 2;
var kNothingChanged = 0,
kFontNameChanged = 1,
kTypefaceChanged = 2,
kSizeChanged = 3,
kTextColorChanged = 4,
kBackgroundColorChanged = 5,
kUnderlineChanged = 6,
kWeightChanged = 7;
var _sharedFontPanel = nil;
// FIXME<!> Locale support
var _availableTraits= [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
_availableSizes = [@"9", @"10", @"11", @"12", @"13", @"14", @"18", @"24", @"36", @"48", @"72", @"96"];
@implementation _CPFontPanelSampleView : CPView
{
CPLayoutManager _layoutManager;
CPTextStorage _textStorage;
CPTextContainer _textContainer;
}
- (id)initWithFrame:(CPRect)rect
{
self = [super initWithFrame:rect];
if (self)
{
_textStorage = [[CPTextStorage alloc] init];
_layoutManager = [[CPLayoutManager alloc] init];
_textContainer = [[CPTextContainer alloc] init];
[_layoutManager addTextContainer:_textContainer];
[_textStorage addLayoutManager:_layoutManager];
}
return self;
}
- (void)setAttributedString:(CPAttributedString)aSting
{
[_textStorage replaceCharactersInRange:CPMakeRange(0, [_textStorage length])
withAttributedString:aSting];
[self setNeedsDisplay:YES];
}
- (void)drawRect:(CPRect)rect
{
var ctx = [[CPGraphicsContext currentContext] graphicsPort],
glyphRange = [_layoutManager glyphRangeForTextContainer:_textContainer],
usedRect = [_layoutManager usedRectForTextContainer:_textContainer],
bounds = [self bounds],
pos = CPMakePoint((bounds.size.width - usedRect.size.width) / 2.0, (bounds.size.height - usedRect.size.height) / 2.0);
CGContextSaveGState(ctx);
CGContextSetFillColor(ctx, [CPColor whiteColor]);
CGContextFillRect(ctx, bounds);
CGContextRestoreGState(ctx);
[_layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:pos];
}
@end
/*!
@ingroup appkit
@class CPFontPanel
*/
@implementation CPFontPanel : CPPanel
{
CPView _toolbarView;
id _fontBrowser;
id _traitBrowser;
id _sizeBrowser;
CPArray _availableFonts;
id _textColorWell;
CPColor _textColor;
int _currentColorButtonTag;
BOOL _setupDone;
int _fontChanges;
_CPFontPanelSampleView _sampleView;
}
/*!
Check if the shared Font panel exists.
*/
+ (BOOL)sharedFontPanelExists
{
return _sharedFontPanel !== nil;
}
/*!
Return the shared Font panel.
*/
+ (CPFontPanel)sharedFontPanel
{
if (!_sharedFontPanel)
_sharedFontPanel = [[CPFontPanel alloc] init];
return _sharedFontPanel;
}
/*! @ignore */
- (id)init
{
self = [super initWithContentRect:CGRectMake(100, 90, 450, 394) styleMask:(CPTitledWindowMask | CPClosableWindowMask /*| CPResizableWindowMask*/ )];
if (self)
{
[[self contentView] setBackgroundColor:[CPColor colorWithWhite:0.95 alpha:1.0]];
[self setTitle:@"Font Panel"];
[self setLevel:CPFloatingWindowLevel];
[self setFloatingPanel:YES];
[self setBecomesKeyOnlyIfNeeded:YES];
[self setMinSize:CGSizeMake(378, 394)];
_availableFonts = [[CPFontManager sharedFontManager] availableFonts];
_textColor = [CPColor blackColor];
_setupDone = NO;
_fontChanges = kNothingChanged;
}
return self;
}
/*! @ignore */
- (void)_setupToolbarView
{
_toolbarView = [[CPView alloc] initWithFrame:CGRectMake(0, kBorderSpacing, CGRectGetWidth([self frame]), kToolbarHeight)];
[_toolbarView setAutoresizingMask: CPViewWidthSizable];
/* text color */
_textColorWell = [[CPColorWell alloc] initWithFrame:CGRectMake(10, 0, 25, 25)];
[_textColorWell setColor:_textColor]; // <!> FIXME: use bindings
[_toolbarView addSubview:_textColorWell];
var colorPanel = [CPColorPanel sharedColorPanel];
[colorPanel setTarget:self];
[colorPanel setAction:@selector(changeColor:)];
}
- (void)_setupBrowser: aBrowser
{
[aBrowser setTarget:self];
[aBrowser setAction:@selector(browserClicked:)];
[aBrowser setDoubleAction:@selector(dblClicked:)];
[aBrowser setAllowsEmptySelection:NO];
[aBrowser setAllowsMultipleSelection: NO];
[aBrowser setDelegate:self];
[[self contentView] addSubview:aBrowser];
}
- (void)_setupContents
{
if (_setupDone)
return;
_setupDone = YES;
[self _setupToolbarView];
var contentView = [self contentView],
label = [CPTextField labelWithTitle:@"Font name"],
contentBounds = [contentView bounds],
upperView = [[CPView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(contentBounds), CGRectGetHeight(contentBounds) - (kBorderSpacing + kToolbarHeight + kInnerSpacing))];
[contentView addSubview:_toolbarView];
_fontBrowser = [[CPBrowser alloc] initWithFrame:CGRectMake(10, 35, 150, 350)];
_traitBrowser = [[CPBrowser alloc] initWithFrame:CGRectMake(155, 35, 150, 350)];
_sizeBrowser = [[CPBrowser alloc] initWithFrame:CGRectMake(300, 35, 140, 350)];
[self _setupBrowser:_fontBrowser];
[self _setupBrowser:_traitBrowser];
[self _setupBrowser:_sizeBrowser];
[[CPNotificationCenter defaultCenter] addObserver:self
selector:@selector(textViewDidChangeSelection:)
name:CPTextViewDidChangeSelectionNotification
object:nil];
}
- (void)textViewDidChangeSelection:(CPNotification)notification
{
[self _refreshWithTextView:[notification object]];
}
- (void)_refreshWithTextView: textView
{
if ([self isVisible])
{
var attribs = [textView typingAttributes],
font = [attribs objectForKey:CPFontAttributeName] || [[textView textStorage] font] || [CPFont systemFontOfSize:12.0];
if (font)
{
var trait = kTypefaceIndex_Normal;
if ([font isItalic] && [font isBold])
trait = kTypefaceIndex_BoldItalic;
else if ([font isItalic])
trait = kTypefaceIndex_Italic;
else if ([font isBold])
trait = kTypefaceIndex_Bold;
[self setCurrentFont: font];
[self setCurrentTrait: trait];
[self setCurrentSize: [font size] + ""]; //cast to string
}
}
}
- (void)orderFront:(id)sender
{
[self _setupContents];
[super orderFront:sender];
[self _refreshWithTextView: [[CPApp keyWindow] firstResponder]];
}
- (void)reloadDefaultFontFamilies
{
_availableFonts = [[CPFontManager sharedFontManager] availableFonts];
}
- (BOOL)worksWhenModal
{
return YES;
}
/*!
@param aFont the font to convert.
@return The converted font or \c aFont if failed to convert.
*/
- (CPFont)panelConvertFont:(CPFont)aFont
{
var newFont = aFont,
index = 0;
switch (_fontChanges)
{
case kFontNameChanged:
newFont = [CPFont fontWithDescriptor:[[aFont fontDescriptor] fontDescriptorByAddingAttributes:
[CPDictionary dictionaryWithObject: [self currentFont] forKey:CPFontNameAttribute]] size:0.0];
break;
case kTypefaceChanged:
index = [self currentTrait];
if (index == kTypefaceIndex_BoldItalic)
newFont = [[CPFontManager sharedFontManager] convertFont:aFont toHaveTrait:CPBoldFontMask | CPItalicFontMask];
else if (index == kTypefaceIndex_Bold)
newFont = [[CPFontManager sharedFontManager] convertFont:aFont toHaveTrait:CPBoldFontMask];
else if (index == kTypefaceIndex_Italic)
newFont = [[CPFontManager sharedFontManager] convertFont:aFont toHaveTrait:CPItalicFontMask];
else
newFont = [[CPFontManager sharedFontManager] convertFont:aFont toNotHaveTrait:CPBoldFontMask | CPItalicFontMask];
break;
case kSizeChanged:
newFont = [[CPFontManager sharedFontManager] convertFont:aFont toSize:[self currentSize]];
break;
case kNothingChanged:
break;
default:
CPLog.trace(@"FIXME: -[" + [self className] + " " + _cmd + "] unhandled _fontChanges: " + _fontChanges);
break;
}
return newFont;
}
- (void)setCurrentSize: aSize
{
[_sizeBrowser selectRow: [_availableSizes indexOfObject: aSize] inColumn:0];
}
- (CPString)currentSize
{
return [_sizeBrowser selectedItem];
}
- (void)setCurrentFont: aFont
{
[_fontBrowser selectRow: [_availableFonts indexOfObject: [aFont familyName]] inColumn:0];
}
- (CPString)currentFont
{
return [_fontBrowser selectedItem];
}
- (void)setCurrentTrait: aTrait
{
var row = 0;
switch (aTrait)
{
case kTypefaceIndex_Italic:
row = 1;
break;
case kTypefaceIndex_Bold:
row = 2;
break;
case kTypefaceIndex_BoldItalic:
row = 3;
break;
}
[_traitBrowser selectRow: row inColumn:0];
}
// FIXME<!> Locale support
- (void)currentTrait
{
var sel = [_traitBrowser selectedItem];
if (sel === "Italic")
return kTypefaceIndex_Italic;
if (sel === "Bold")
return kTypefaceIndex_Bold;
if (sel === "Bold Italic")
return kTypefaceIndex_BoldItalic;
return kTypefaceIndex_Normal;
}
/*!
Set the selected font in Font panel.
@param font the selected font
@param flag if \c the current selection have multiple fonts.
*/
- (void)setPanelFont:(CPFont)font isMultiple:(BOOL)flag
{
[self _setupContents];
if ([self currentFont] !== [font familyName])
[self setCurrentFont:[font familyName]];
if ([self currentSize] != [font size])
[self setCurrentSize:[font size]];
var typefaceIndex = kTypefaceIndex_Normal,
symbolicTraits = [[font fontDescriptor] symbolicTraits];
if ((symbolicTraits & CPFontItalicTrait) && (symbolicTraits & CPFontBoldTrait))
typefaceIndex = kTypefaceIndex_BoldItalic;
else if (symbolicTraits & CPFontItalicTrait)
typefaceIndex = kTypefaceIndex_Italic;
else if (symbolicTraits & CPFontBoldTrait)
typefaceIndex = kTypefaceIndex_Bold;
if ([self currentTrait] != typefaceIndex)
[self setCurrentTrait: typefaceIndex ];
[_sampleView setAttributedString:
[[CPAttributedString alloc] initWithString:[font familyName]
attributes:[CPDictionary dictionaryWithObjects:[font, [CPColor blackColor]] forKeys:[CPFontAttributeName, CPForegroundColorAttributeName]]]
];
_fontChanges = kNothingChanged;
}
- (void)changeColor:(id)sender
{
_textColor = [sender color];
_fontChanges = kTextColorChanged;
[[CPFontManager sharedFontManager] modifyFontViaPanel:self];
}
////////////////////////////////////////////////////////////////////
// TODO: ask CPFontManager for traits //
- (void)browserClicked:(id)aBrowser
{
if (aBrowser === _fontBrowser)
{
_fontChanges = kFontNameChanged;
[[CPFontManager sharedFontManager] modifyFontViaPanel:self];
}
else if (aBrowser === _traitBrowser)
{
_fontChanges = kTypefaceChanged;
[[CPFontManager sharedFontManager] modifyFontViaPanel:self];
}
else if (aBrowser === _sizeBrowser)
{
_fontChanges = kSizeChanged;
[[CPFontManager sharedFontManager] modifyFontViaPanel:self];
}
}
- (void)dblClicked:(id)sender
{
// alert("DOUBLE");
}
- (id)browser:(id)aBrowser numberOfChildrenOfItem:(id)anItem
{
if (aBrowser === _fontBrowser)
return [_availableFonts count];
if (aBrowser === _traitBrowser)
return [_availableTraits count]
else
return [_availableSizes count]
}
- (id)browser:(id)aBrowser child:(int)index ofItem:(id)anItem
{
if (aBrowser === _fontBrowser)
return [_availableFonts objectAtIndex:index];
if (aBrowser === _traitBrowser)
return [_availableTraits objectAtIndex:index];
return [_availableSizes objectAtIndex:index];
}
- (id)browser:(id)aBrowser objectValueForItem:(id)anItem
{
return anItem;
}
- (BOOL)browser:(id)aBrowser isLeafItem:(id)anItem
{
return YES;
}
@end
+1419
View File
File diff suppressed because it is too large Load Diff
+186
View File
@@ -0,0 +1,186 @@
/*
* 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.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
@import <Foundation/CPObject.j>
var _sharedDefaultParagraphStyle,
_defaultTabStopArray;
CPLeftTabStopType = 0;
/*
CPLeftTextAlignment = 0;
CPCenterTextAlignment = 1;
CPRightTextAlignment = 2;
*/
CPParagraphStyleAttributeName = @"CPParagraphStyleAttributeName";
@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;
}
- (id)initWithCoder:(id)aCoder
{
self = [self init];
if (self)
{
_type = [aCoder decodeIntForKey:"_type"];
_location = [aCoder decodeDoubleForKey:"_location"];
}
return self;
}
- (void)encodeWithCoder:(id)aCoder
{
[aCoder encodeInt:_type forKey:"_type"];
[aCoder encodeDouble:_location forKey:"_location"];
}
@end
@implementation CPParagraphStyle : 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);
}
+ (CPParagraphStyle)defaultParagraphStyle
{
if (!_sharedDefaultParagraphStyle)
_sharedDefaultParagraphStyle = [self new];
return _sharedDefaultParagraphStyle;
}
+ (CPArray)_defaultTabStops
{
if (!_defaultTabStopArray)
{
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]);
}
}
return _defaultTabStopArray;
}
- (void)addTabStop:(CPTextTab)aStop
{
_tabStops.push(aStop);
}
- (void)_initWithDefaults
{
_alignment = CPLeftTextAlignment;
_tabStops = [[[self class] _defaultTabStops] copy];
}
- (id)init
{
[self _initWithDefaults];
return self;
}
- (id)copy
{
var other = [[self class] alloc];
return [other initWithParagraphStyle:self];
}
- initWithParagraphStyle:(CPParagraphStyle) other
{
other._tabStops = [_tabStops copy];
other._alignment = _alignment;
other._firstLineHeadIndent = _firstLineHeadIndent;
other._headIndent = _headIndent;
other._tailIndent = _tailIndent;
other._paragraphSpacing = _paragraphSpacing;
other._minimumLineHeight = _minimumLineHeight;
other._maximumLineHeight = _maximumLineHeight;
other._lineSpacing = _lineSpacing;
return self;
}
- (id)initWithCoder:(id)aCoder
{
self = [self init];
if (self)
{
_tabStops = [aCoder decodeObjectForKey:"_tabStops"];
_alignment = [aCoder decodeIntForKey:"_alignment"];
_firstLineHeadIndent = [aCoder decodeIntForKey:"_firstLineHeadIndent"];
_headIndent = [aCoder decodeIntForKey:"_headIndent"];
_tailIndent = [aCoder decodeIntForKey:"_tailIndent"];
_paragraphSpacing = [aCoder decodeIntForKey:"_paragraphSpacing"];
_minimumLineHeight = [aCoder decodeIntForKey:"_minimumLineHeight"];
_maximumLineHeight = [aCoder decodeIntForKey:"_maximumLineHeight"];
_lineSpacing = [aCoder decodeIntForKey:"_lineSpacing"];
}
return self;
}
- (void)encodeWithCoder:(id)aCoder
{
[aCoder encodeInt:_alignment forKey:"_alignment"];
[aCoder encodeObject:_tabStops forKey:"_tabStops"];
[aCoder encodeInt:_firstLineHeadIndent forKey:"_firstLineHeadIndent"];
[aCoder encodeInt:_headIndent forKey:"_headIndent"];
[aCoder encodeInt:_tailIndent forKey:"_tailIndent"];
[aCoder encodeInt:_paragraphSpacing forKey:"_paragraphSpacing"];
[aCoder encodeInt:_minimumLineHeight forKey:"_minimumLineHeight"];
[aCoder encodeInt:_maximumLineHeight forKey:"_maximumLineHeight"];
[aCoder encodeInt:_lineSpacing forKey:"_lineSpacing"];
}
@end
+209
View File
@@ -0,0 +1,209 @@
/*
* CPTextContainer.j
* AppKit
*
* Created by Emmanuel Maillard on 27/02/2010.
* Copyright Emmanuel Maillard 2010.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
@import "CPLayoutManager.j"
/*
@global
@group CPLineSweepDirection
*/
CPLineSweepLeft = 0;
/*
@global
@group CPLineSweepDirection
*/
CPLineSweepRight = 1;
/*
@global
@group CPLineSweepDirection
*/
CPLineSweepDown = 2;
/*
@global
@group CPLineSweepDirection
*/
CPLineSweepUp = 3;
/*
@global
@group CPLineMovementDirection
*/
CPLineDoesntMoves = 0;
/*
@global
@group CPLineMovementDirection
*/
CPLineMovesLeft = 1;
/*
@global
@group CPLineMovementDirection
*/
CPLineMovesRight = 2;
/*
@global
@group CPLineMovementDirection
*/
CPLineMovesDown = 3;
/*
@global
@group CPLineMovementDirection
*/
CPLineMovesUp = 4;
/*!
@ingroup appkit
@class CPTextContainer
*/
@implementation CPTextContainer : CPObject
{
CPSize _size;
CPTextView _textView;
CPLayoutManager _layoutManager;
float _lineFragmentPadding;
}
- (id)initWithContainerSize:(CPSize)aSize
{
self = [super init];
if (self)
{
_size = aSize;
_lineFragmentPadding = 0.0;
}
return self;
}
- (id)init
{
return [self initWithContainerSize:CPMakeSize(1e7, 1e7)];
}
- (CPSize)containerSize
{
return _size;
}
- (void)setContainerSize:(CPSize)someSize
{
var oldSize = _size;
_size = someSize;
if (oldSize.width != _size.width)
[_layoutManager invalidateLayoutForCharacterRange:CPMakeRange(0,[[_layoutManager textStorage] length])
isSoft:NO
actualCharacterRange:NULL];
}
- (void)setWidthTracksTextView:(BOOL)flag
{
//<!> fixme: Controls whether the receiver adjusts the width of its bounding rectangle when its text view is resized.
}
- (void)setTextView:(CPTextView)aTextView
{
if (_textView)
{
[self _removeAllLines];
[_textView setTextContainer:nil];
}
_textView = aTextView;
if (_textView != nil)
[_textView setTextContainer:self];
[_layoutManager textContainerChangedTextView:self];
}
- (CPTextView)textView
{
return _textView;
}
- (void)setLayoutManager:(CPLayoutManager)aManager
{
if (_layoutManager === aManager)
return;
_layoutManager = aManager;
}
- (CPLayoutManager)layoutManager
{
return _layoutManager;
}
- (void)setLineFragmentPadding:(float)aFloat
{
_lineFragmentPadding = aFloat;
}
- (float)lineFragmentPadding
{
return _lineFragmentPadding;
}
- (BOOL)containsPoint:(CPPoint)aPoint
{
return CPRectContainsPoint(CPRectMake(0, 0, _size.width, _size.height), aPoint);
}
- (BOOL)isSimpleRectangularTextContainer
{
return YES;
}
- (CPRect)lineFragmentRectForProposedRect:(CPRect)proposedRect
sweepDirection:(CPLineSweepDirection)sweep
movementDirection:(CPLineMovementDirection)movement
remainingRect:(CPRectPointer)remainingRect
{
var resultRect = CPRectCreateCopy(proposedRect);
if (sweep != CPLineSweepRight || movement != CPLineMovesDown)
{
CPLog.trace(@"FIXME: unsupported sweep ("+sweep+") or movement ("+movement+")");
return CPRectMakeZero();
}
if (resultRect.origin.x + resultRect.size.width > _size.width)
resultRect.size.width = _size.width - resultRect.origin.x;
if (resultRect.size.width < 0)
resultRect = CPRectMakeZero();
if (remainingRect)
{
remainingRect.origin.x = resultRect.origin.x + resultRect.size.width;
remainingRect.origin.y = resultRect.origin.y;
remainingRect.size.height = resultRect.size.height;
remainingRect.size.width = _size.width - (resultRect.origin.x + resultRect.size.width);
}
return resultRect;
}
@end
+295
View File
@@ -0,0 +1,295 @@
/*
* CPTextStorage.j
* AppKit
*
* Created by Emmanuel Maillard on 27/02/2010.
* Copyright Emmanuel Maillard 2010.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
//@import <Foundation/CPAttributedString.j>
@import "CPLayoutManager.j"
CPTextStorageEditedAttributes = 1;
CPTextStorageEditedCharacters = 2;
CPTextStorageWillProcessEditingNotification = @"CPTextStorageWillProcessEditingNotification";
CPTextStorageDidProcessEditingNotification = @"CPTextStorageDidProcessEditingNotification";
/*
FIXME: move these to CPAttributed string
Make use of attributed keys in AppKit
*/
CPFontAttributeName = @"CPFontAttributeName";
CPForegroundColorAttributeName = @"CPForegroundColorAttributeName";
CPBackgroundColorAttributeName = @"CPBackgroundColorAttributeName";
CPShadowAttributeName = @"CPShadowAttributeName";
CPUnderlineStyleAttributeName = @"CPUnderlineStyleAttributeName";
CPSuperscriptAttributeName = @"CPSuperscriptAttributeName";
CPBaselineOffsetAttributeName = @"CPBaselineOffsetAttributeName";
CPAttachmentAttributeName = @"CPAttachmentAttributeName";
CPLigatureAttributeName = @"CPLigatureAttributeName";
CPKernAttributeName = @"CPKernAttributeName";
/*!
@ingroup appkit
@class CPTextStorage
*/
@implementation CPTextStorage : CPAttributedString
{
CPMutableArray _layoutManagers;
id _delegate;
int _changeInLength;
unsigned _editedMask;
CPRange _editedRange;
int _editCount; // {begin,end}Editing counter
CPFont _font;
CPColor _foregroundColor;
}
- (id)initWithString:(CPString)aString attributes:(CPDictionary)attributes
{
self = [super initWithString:aString attributes:attributes];
if (self)
{
_layoutManagers = [[CPMutableArray alloc] init];
_editedRange = CPMakeRange(CPNotFound, 0);
_changeInLength = 0;
_editedMask = 0;
}
return self;
}
- (id)initWithString:(CPString)aString
{
return [self initWithString:aString attributes:nil];
}
- (id)init
{
return [self initWithString:@"" attributes:nil];
}
- (id)delegate
{
return _delegate;
}
- (void)setDelegate:(id)aDelegate
{
if (_delegate === aDelegate)
return;
var notificationCenter = [CPNotificationCenter defaultCenter];
if (_delegate && aDelegate === nil)
{
[notificationCenter removeObserver:_delegate name:CPTextStorageWillProcessEditingNotification object:self];
[notificationCenter removeObserver:_delegate name:CPTextStorageDidProcessEditingNotification object:self];
}
_delegate = aDelegate;
if (_delegate)
{
if ([_delegate respondsToSelector:@selector(textStorageWillProcessEditing:)])
[notificationCenter addObserver:_delegate selector:@selector(textStorageWillProcessEditing:) name:CPTextStorageWillProcessEditingNotification object:self];
if ([_delegate respondsToSelector:@selector(textStorageDidProcessEditing:)])
[notificationCenter addObserver:_delegate selector:@selector(textStorageDidProcessEditing:) name:CPTextStorageDidProcessEditingNotification object:self];
}
}
- (void)addLayoutManager:(CPLayoutManager)aManager
{
if (![_layoutManagers containsObject:aManager])
{
[aManager setTextStorage:self];
[_layoutManagers addObject:aManager];
}
}
- (void)removeLayoutManager:(CPLayoutManager)aManager
{
if ([_layoutManagers containsObject:aManager])
{
[aManager setTextStorage:nil];
[_layoutManagers removeObject:aManager];
}
}
- (CPArray)layoutManagers
{
return _layoutManagers;
}
- (CPRange)editedRange
{
return _editedRange;
}
- (int)changeInLength
{
return _changeInLength;
}
- (unsigned)editedMask
{
return _editedMask;
}
- (void)invalidateAttributesInRange:(CPRange)aRange
{
/* FIXME: stub */
}
- (void)processEditing
{
[[CPNotificationCenter defaultCenter] postNotificationName:CPTextStorageWillProcessEditingNotification
object:self];
[self invalidateAttributesInRange:[self editedRange]];
[[CPNotificationCenter defaultCenter] postNotificationName:CPTextStorageDidProcessEditingNotification
object:self];
var c = [_layoutManagers count];
for (var i = 0; i < c; i++)
{
[[_layoutManagers objectAtIndex:i] textStorage:self
edited:_editedMask
range:_editedRange
changeInLength:_changeInLength
invalidatedRange:_editedRange];
}
_editedRange.location = CPNotFound;
_editedMask = 0;
_changeInLength = 0;
}
- (void)beginEditing
{
if (_editCount == 0)
_editedRange = CPMakeRange(CPNotFound, 0);
_editCount++;
}
- (void)endEditing
{
_editCount--;
if (_editCount == 0)
[self processEditing];
}
- (void)edited:(unsigned)editedMask range:(CPRange)aRange changeInLength:(int)lengthChange
{
if (_editCount == 0) /* used outside a beginEditing/endEditing */
{
_editedMask = editedMask;
_changeInLength = lengthChange;
aRange.length += lengthChange;
_editedRange = aRange;
[self processEditing];
}
else
{
_editedMask |= editedMask;
_changeInLength += lengthChange;
aRange.length += lengthChange;
if (_editedRange.location == CPNotFound)
_editedRange = aRange;
else
_editedRange = CPUnionRange(_editedRange,aRange);
}
}
- (void)removeAttribute:(id)anAttribute range:(CPRange)aRange
{
[self beginEditing];
[super removeAttribute:anAttribute range:aRange];
[self edited:CPTextStorageEditedAttributes range:aRange changeInLength:0];
[self endEditing];
}
- (void)addAttributes:(CPDictionary)aDictionary range:(CPRange)aRange
{
[self beginEditing];
[super addAttributes:aDictionary range:aRange];
[self edited:CPTextStorageEditedAttributes range:aRange changeInLength:0];
[self endEditing];
}
- (void)deleteCharactersInRange:(CPRange)aRange
{
[self beginEditing];
[super deleteCharactersInRange:aRange];
[self edited:CPTextStorageEditedCharacters range:aRange changeInLength:-aRange.length];
[self endEditing];
}
- (void)replaceCharactersInRange:(CPRange)aRange withString:(CPString)aString
{
[self beginEditing];
[super replaceCharactersInRange: aRange withString: aString];
[self edited: CPTextStorageEditedCharacters range:aRange changeInLength:([aString length] - aRange.length)];
[self endEditing];
}
- (void)replaceCharactersInRange:(CPRange)aRange withAttributedString:(CPAttributedString)aString
{
[self beginEditing];
[super replaceCharactersInRange: aRange withAttributedString:aString];
[self edited:(CPTextStorageEditedAttributes | CPTextStorageEditedCharacters) range:aRange changeInLength:([aString length] - aRange.length)];
[self endEditing];
}
- (void)setFont:(CPFont)aFont
{
_font = aFont;
}
- (CPFont)font
{
return _font;
}
- (void)setForegroundColor:(CPColor)color
{
_foregroundColor = color;
}
- (CPColor)foregroundColor
{
return _foregroundColor;
}
- (CPAttributedString)attributedSubstringFromRange:(CPRange)aRange
{
if (!aRange.length)
return [CPAttributedString new];
return [super attributedSubstringFromRange:aRange];
}
@end
+1709
View File
File diff suppressed because it is too large Load Diff
+393
View File
@@ -0,0 +1,393 @@
/*
* CPTypesetter.j
* AppKit
*
* Created by Daniel Boehringer on 27/12/2013.
* All modifications copyright Daniel Boehringer 2013.
* Based on original work by
* Emmanuel Maillard on 27/02/2010.
* Copyright Emmanuel Maillard 2010.
*
* FIXME: paragraphStyle indent information is currently not properly respected
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
@import <Foundation/CPObject.j>
@import "CPTextStorage.j"
@import "CPParagraphStyle.j"
/*
CPTypesetterControlCharacterAction
*/
CPTypesetterZeroAdvancementAction = (1 << 0);
CPTypesetterWhitespaceAction = (1 << 1);
CPSTypesetterHorizontalTabAction = (1 << 2);
CPTypesetterLineBreakAction = (1 << 3);
CPTypesetterParagraphBreakAction = (1 << 4);
CPTypesetterContainerBreakAction = (1 << 5);
var _measuringContext;
var _measuringContextFont;
var _isCanvasSizingInvalid = 0;
var _didTestCanvasSizingValid;
function _widthOfStringForFont(aString, aFont)
{
if (!_measuringContext)
_measuringContext = CGBitmapGraphicsContextCreate();
if (!_didTestCanvasSizingValid && CPFeatureIsCompatible(CPHTMLCanvasFeature))
{
var teststring = "0123456879abcdefghiklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,.-()";
_didTestCanvasSizingValid = YES;
_measuringContext.font = [aFont cssString];
_isCanvasSizingInvalid = [teststring sizeWithFont:aFont].width != _measuringContext.measureText(teststring).width;
}
if (!CPFeatureIsCompatible(CPHTMLCanvasFeature) || _isCanvasSizingInvalid) // measuring with canvas is _much_ faster on chrome
return [aString sizeWithFont:aFont];
if (_measuringContextFont !== aFont)
{
_measuringContextFont = aFont
_measuringContext.font = [aFont cssString];
}
return _measuringContext.measureText(aString);
}
var CPSystemTypesetterFactory = Nil;
@implementation CPTypesetter : CPObject
{
}
+ (id)sharedSystemTypesetter
{
return [CPSystemTypesetterFactory sharedInstance];
}
+ (void)_setSystemTypesetterFactory:(Class)aClass
{
CPSystemTypesetterFactory = aClass;
}
+ (void)initialize
{
[CPTypesetter _setSystemTypesetterFactory:[CPSimpleTypesetter class]];
}
- (CPTypesetterControlCharacterAction)actionForControlCharacterAtIndex:(unsigned)charIndex
{
return CPTypesetterZeroAdvancementAction;
}
- (CPLayoutManager)layoutManager
{
return nil;
}
- (CPTextContainer)currentTextContainer
{
return nil;
}
- (CPArray)textContainers
{
return nil;
}
- (void)layoutGlyphsInLayoutManager:(CPLayoutManager)layoutManager
startingAtGlyphIndex:(unsigned)startGlyphIndex
maxNumberOfLineFragments:(unsigned)maxNumLines
nextGlyphIndex:(UIntegerPointer)nextGlyph
{
CPLog.error(@"-[CPTypesetter subclass responsibility");
}
@end
var _sharedSimpleTypesetter = nil;
@implementation CPSimpleTypesetter:CPTypesetter
{
CPLayoutManager _layoutManager;
CPTextContainer _currentTextContainer;
CPTextStorage _textStorage;
CPRange _attributesRange;
CPDictionary _currentAttributes;
CPFont _currentFont;
CPParagraphStyle _currentParagraph;
float _lineHeight;
float _lineBase;
float _lineWidth;
unsigned _indexOfCurrentContainer;
}
+ (id)sharedInstance
{
if (_sharedSimpleTypesetter === nil)
_sharedSimpleTypesetter = [[CPSimpleTypesetter alloc] init];
return _sharedSimpleTypesetter;
}
- (CPLayoutManager)layoutManager
{
return _layoutManager;
}
- (CPTextContainer)currentTextContainer
{
return _currentTextContainer;
}
- (CPArray)textContainers
{
return [_layoutManager textContainers];
}
- (CPTextTab)textTabForWidth:(double)aWidth writingDirection:(CPWritingDirection)direction
{
var tabStops = [_currentParagraph tabStops];
if (!tabStops)
tabStops = [CPParagraphStyle _defaultTabStops];
var i,
l = tabStops.length;
if (aWidth > tabStops[l-1]._location)
return nil;
for (i = l-1; i >= 0; i--)
{
if (aWidth > tabStops[i]._location)
{
if (i + 1 < l)
return tabStops[i + 1];
}
}
return nil;
}
- (BOOL)_flushRange:(CPRange)lineRange
lineOrigin:(CPPoint)lineOrigin
currentContainerSize:(CPSize)containerSize
advancements:(CPArray)advancements
lineCount:(unsigned)lineCount
{
[_layoutManager setTextContainer:_currentTextContainer forGlyphRange:lineRange]; // creates a new lineFragment
var rect = CPRectMake(lineOrigin.x, lineOrigin.y, _lineWidth, _lineHeight);
[_layoutManager setLineFragmentRect: rect forGlyphRange:lineRange usedRect:rect];
var myX = 0;
switch ([_currentParagraph alignment])
{
case CPLeftTextAlignment:
myX = 0;
break;
case CPCenterTextAlignment:
myX = (containerSize.width - _lineWidth) / 2;
break;
case CPRightTextAlignment:
myX = containerSize.width - _lineWidth;
break;
}
[_layoutManager setLocation:CPMakePoint(myX, _lineBase) forStartOfGlyphRange:lineRange];
[_layoutManager _setAdvancements:advancements forGlyphRange:lineRange];
if (!lineCount)
return NO;
return ([_layoutManager _rescuingInvalidFragmentsWasPossibleForGlyphRange:lineRange]);
}
- (void)layoutGlyphsInLayoutManager:(CPLayoutManager)layoutManager
startingAtGlyphIndex:(unsigned)glyphIndex
maxNumberOfLineFragments:(unsigned)maxNumLines
nextGlyphIndex:(UIntegerReference)nextGlyph
{
_layoutManager = layoutManager;
_textStorage = [_layoutManager textStorage];
_indexOfCurrentContainer = MAX(0, [[_layoutManager textContainers]
indexOfObject:[_layoutManager textContainerForGlyphAtIndex:glyphIndex effectiveRange:nil withoutAdditionalLayout:YES]
inRange:CPMakeRange(0, [[_layoutManager textContainers] count])]);
_currentTextContainer = [[_layoutManager textContainers] objectAtIndex:_indexOfCurrentContainer];
_attributesRange = CPMakeRange(0, 0);
_lineHeight = 0;
_lineBase = 0;
_lineWidth = 0;
var containerSize = [_currentTextContainer containerSize],
lineRange = CPMakeRange(glyphIndex, 0),
wrapRange = CPMakeRange(0, 0),
wrapWidth = 0,
isNewline = NO,
isTabStop = NO,
isWordWrapped = NO,
numberOfGlyphs= [_textStorage length],
leading;
var numLines = 0,
theString = [_textStorage string],
lineOrigin,
ascent, descent;
var advancements = [],
prevRangeWidth = 0,
measuringRange = CPMakeRange(glyphIndex, 0),
currentAnchor = 0,
_previousFont = nil;
if (glyphIndex > 0)
lineOrigin = CPPointCreateCopy([_layoutManager lineFragmentRectForGlyphAtIndex:glyphIndex effectiveRange:nil].origin);
else if ([_layoutManager extraLineFragmentTextContainer])
lineOrigin = CPPointMake(0, [_layoutManager extraLineFragmentUsedRect].origin.y);
else
lineOrigin = CPPointMake(0, 0);
[_layoutManager _removeInvalidLineFragments];
if (![_textStorage length])
return;
for (; numLines != maxNumLines && glyphIndex < numberOfGlyphs; glyphIndex++)
{
if (!CPLocationInRange(glyphIndex, _attributesRange))
{
_currentAttributes = [_textStorage attributesAtIndex:glyphIndex effectiveRange:_attributesRange];
_currentFont = [_currentAttributes objectForKey:CPFontAttributeName];
_currentParagraph = [_currentAttributes objectForKey:CPParagraphStyleAttributeName] || [CPParagraphStyle defaultParagraphStyle];
if (!_currentFont)
_currentFont = [_textStorage font];
ascent = ["x" sizeWithFont:_currentFont].height; //FIXME
descent = 0; //FIXME
leading = (ascent - descent) * 0.2; // FAKE leading
}
if (_previousFont !== _currentFont)
{
measuringRange = CPMakeRange(glyphIndex, 0);
currentAnchor = prevRangeWidth;
_previousFont = _currentFont;
}
lineRange.length++;
measuringRange.length++;
var currentChar = theString[glyphIndex], // use pure javascript methods for performance reasons
rangeWidth = _widthOfStringForFont(theString.substr(measuringRange.location, measuringRange.length), _currentFont).width + currentAnchor;
switch (currentChar) // faster than sending actionForControlCharacterAtIndex: called for each char.
{
case '\n':
isNewline = YES;
break;
case '\t':
{
isTabStop = YES;
var nextTab = [self textTabForWidth:rangeWidth + lineOrigin.x writingDirection:0];
if (nextTab)
{
rangeWidth = nextTab._location - lineOrigin.x;
}
else
rangeWidth += 28; //FIXME
} // fallthrough intentional
case ' ':
wrapRange = CPMakeRangeCopy(lineRange);
wrapWidth = rangeWidth;
break;
}
advancements.push(rangeWidth - prevRangeWidth);
prevRangeWidth = _lineWidth = rangeWidth;
if (lineOrigin.x + rangeWidth > containerSize.width)
{
if (wrapWidth)
{
lineRange = wrapRange;
_lineWidth = wrapWidth;
}
isNewline = YES;
isWordWrapped = YES;
glyphIndex = CPMaxRange(lineRange) - 1;
}
_lineHeight = MAX(_lineHeight, ascent - descent + leading);
_lineBase = MAX(_lineBase, ascent);
if (isNewline || isTabStop)
{
if ([self _flushRange:lineRange lineOrigin:lineOrigin currentContainerSize:containerSize advancements:advancements lineCount:numLines])
return;
if (isTabStop)
{
lineOrigin.x += rangeWidth;
isTabStop = NO;
}
if (isNewline)
{
if ([_currentParagraph minimumLineHeight])
_lineHeight = MAX(_lineHeight, [_currentParagraph minimumLineHeight]);
if ([_currentParagraph maximumLineHeight])
_lineHeight = MIN(_lineHeight, [_currentParagraph maximumLineHeight]);
lineOrigin.y += _lineHeight;
if ([_currentParagraph lineSpacing])
lineOrigin.y += [_currentParagraph lineSpacing];
if (lineOrigin.y > [_currentTextContainer containerSize].height)
{
_currentTextContainer = [[_layoutManager textContainers] objectAtIndex: ++_indexOfCurrentContainer];
}
lineOrigin.x = 0;
numLines++;
isNewline = NO;
}
_lineWidth = 0;
advancements = [];
prevRangeWidth = 0;
currentAnchor = 0;
_lineHeight = 0;
_lineBase = 0;
_previousFont = nil;
lineRange = CPMakeRange(glyphIndex + 1, 0);
wrapRange = CPMakeRange(0, 0);
wrapWidth = 0;
isWordWrapped = NO;
}
}
// this is to "flush" the remaining characters
if (lineRange.length)
[self _flushRange:lineRange lineOrigin:lineOrigin currentContainerSize:containerSize advancements:advancements lineCount:numLines];
if ([theString.charAt(theString.length - 1) ==="\n"])
{
var rect = CPRectMake(0, lineOrigin.y, containerSize.width, [_layoutManager._lineFragments lastObject]._usedRect.size.height); // fixme: row-height is crudely hacked
[_layoutManager setExtraLineFragmentRect:rect usedRect:rect textContainer:_currentTextContainer];
}
}
@end
+700
View File
@@ -0,0 +1,700 @@
/* RTFParser.j
Parse a RTF string into a CPAttributedString
Copyright (C) 2014 Daniel Boehringer
FIXME: this really sucks and should be redone using a 'real' parser
e.g. using zaach/jison on github
* all paragraph spacing information is currently not parsed
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
//@import <Foundation/CPAttributedString.j>
// Hold the attributes of the current run
@implementation _RTFAttribute: CPObject
{
CPRange _range;
CPParagraphStyle paragraph;
CPColor fgColour;
CPColor bgColour;
CPColor ulColour;
CPString fontName;
unsigned fontSize;
BOOL bold;
BOOL italic;
BOOL underline;
BOOL strikethrough;
BOOL script;
BOOL _tabChanged;
}
- (id) init
{
[self resetFont];
[self resetParagraphStyle];
_range = CPMakeRange(0, 0);
return self;
}
- (id) copy
{
var mynew = [_RTFAttribute new];
mynew.paragraph = [paragraph copy];
mynew.fontName = fontName;
mynew.fgColour = fgColour;
mynew.bgColour = bgColour;
mynew.ulColour = ulColour;
return mynew;
}
- (CPFont)currentFont
{
var font = [CPFont _fontWithName:fontName size:fontSize bold:bold italic:italic];
if (font == nil)
{
/* Before giving up and using a default font, we try if this is
* not the case of a font with a composite name, such as
* 'Helvetica-Light'. In that case, even if we don't have
* exactly an 'Helvetica-Light' font family, we might have an
* 'Helvetica' one. */
var range = [fontName rangeOfString:@"-"];
if (range.location != CPNotFound)
{
var fontFamily = [fontName substringToIndex: range.location];
font = [[CPFontManager sharedFontManager] fontWithFamily: fontFamily
traits: traits
weight: weight
size: fontSize];
}
if (font == nil)
{
console.log(@"RTFParser",
@"Could not find font %@ size %f traits %d weight %d",
fontName, fontSize, traits, weight);
/* Last resort, default font. :-( */
font = [CPFont systemFontOfSize: fontSize];
}
}
return font;
}
- (CPNumber)script
{
return [CPNumber numberWithInt: script];
}
- (CPNumber)underline
{
if (underline != 0)
return [CPNumber numberWithInteger: underline];
else
return nil;
}
- (CPNumber)strikethrough
{
if (strikethrough != 0)
return [CPNumber numberWithInteger: strikethrough];
else
return nil;
}
- (void)resetParagraphStyle
{
paragraph = [[CPParagraphStyle defaultParagraphStyle] copy];
}
- (void)resetFont
{
var font = [CPFont systemFontOfSize:12];
fontName = [font familyName];
fontSize = 12.0;
italic = NO;
bold = NO;
underline = 0;
strikethrough = 0;
script = 0;
}
- (void)addTab:(float)location type:(CPTextTabType)type
{
var tab = [[CPTextTab alloc] initWithType: CPLeftTabStopType
location: location];
if (!_tabChanged)
{
[paragraph setTabStops:[tab]];
_tabChanged = YES;
}
else
{
[paragraph addTabStop: tab];
}
}
-(CPDictionary) dictionary
{
var ret = @{};
[ret setObject:[self currentFont] forKey:CPFontAttributeName];
[ret setObject:paragraph forKey:CPParagraphStyleAttributeName];
if (fgColour)
[ret setObject:fgColour forKey:CPForegroundColorAttributeName];
return ret;
}
@end
// based on https://github.com/lazygyu/RTF-parser
var kRTFParserType_char = 0,
kRTFParserType_dest = 1,
kRTFParserType_prop = 2,
kRTFParserType_spec = 3;
// Keyword descriptions
var kRgsymRtf = {
// keyword dflt fPassDflt kwd idx
"b" : [ "b", 1, false, kRTFParserType_prop, "propBold"],
"ul" : [ "ul", 1, false, kRTFParserType_prop, "propUnderline"],
"i" : [ "i", 1, false, kRTFParserType_prop, "propItalic"],
"li" : [ "li", 0, false, kRTFParserType_prop, "propPgnFormat"],
"pgnucltr" : [ "pgnucltr", "pgULtr", true, kRTFParserType_prop, "propPgnFormat"],
"pgnlcltr" : [ "pgnlcltr", "pgLLtr", true, kRTFParserType_prop, "propPgnFormat"],
"qc" : [ "qc", "justC", true, kRTFParserType_prop, "propJust"],
"ql" : [ "ql", "justL", true, kRTFParserType_prop, "propJust"],
"qr" : [ "qr", "justR", true, kRTFParserType_prop, "propJust"],
"qj" : [ "qj", "justF", true, kRTFParserType_prop, "propJust"],
"paperw" : [ "paperw", 12240, false, kRTFParserType_prop, "propXaPage"],
"paperh" : [ "paperh", 15480, false, kRTFParserType_prop, "propYaPage"],
"margl" : [ "margl", 1800, false, kRTFParserType_prop, "propXaLeft"],
"margr" : [ "margr", 1800, false, kRTFParserType_prop, "propXaRight"],
"margt" : [ "margt", 1440, false, kRTFParserType_prop, "propYaTop"],
"margb" : [ "margb", 1440, false, kRTFParserType_prop, "propYaBottom"],
"pgnstart" : [ "pgnstart", 1, true, kRTFParserType_prop, "propPgnStart"],
"facingp" : [ "facingp", 1, true, kRTFParserType_prop, "propFacingp"],
"landscape" : [ "landscape",1, true, kRTFParserType_prop, "propLandscape"],
"par" : [ "par", 0, false, kRTFParserType_char, "\n"],
"pard" : [ "pard", 0, false, kRTFParserType_prop, "propDefaultPara"],
"\0x0a" : [ "\0x0a", 0, false, kRTFParserType_char, "\n"],
"\0x0d" : [ "\0x0d", 0, false, kRTFParserType_char, ""],
"tab" : [ "tab", 0, false, kRTFParserType_char, "\t"],
"ldblquote" : [ "ldblquote",0, false, kRTFParserType_char, '"'],
"rdblquote" : [ "rdblquote",0, false, kRTFParserType_char, '"'],
"bin" : [ "bin", 0, false, kRTFParserType_spec, "ipfnBin"],
"*" : [ "*", 0, false, kRTFParserType_spec, "ipfnDestSkip"],
"'" : [ "'", 0, false, kRTFParserType_spec, "ipfnHex"],
"author" : [ "author", 0, false, kRTFParserType_dest, "destSkip"],
"buptim" : [ "buptim", 0, false, kRTFParserType_dest, "destSkip"],
"colortbl" : [ "colortbl", 0, false, kRTFParserType_dest, "destSkip"],
"comment" : [ "comment", 0, false, kRTFParserType_dest, "destSkip"],
"creatim" : [ "creatim", 0, false, kRTFParserType_dest, "destSkip"],
"doccomm" : [ "doccomm", 0, false, kRTFParserType_dest, "destSkip"],
"fonttbl" : [ "fonttbl", 0, false, kRTFParserType_dest, "destSkip"],
"footer" : [ "footer", 0, false, kRTFParserType_dest, "destSkip"],
"footerf" : [ "footerf", 0, false, kRTFParserType_dest, "destSkip"],
"footerl" : [ "footerl", 0, false, kRTFParserType_dest, "destSkip"],
"footerr" : [ "footerr", 0, false, kRTFParserType_dest, "destSkip"],
"footnote" : [ "footnote", 0, false, kRTFParserType_dest, "destSkip"],
"ftncn" : [ "ftncn", 0, false, kRTFParserType_dest, "destSkip"],
"ftnsep" : [ "ftnsep", 0, false, kRTFParserType_dest, "destSkip"],
"ftnsepc" : [ "ftnsepc", 0, false, kRTFParserType_dest, "destSkip"],
"fprq" : [ "fprq", 0, false, kRTFParserType_dest, "destSkip"],
"fcharset" : [ "fcharset", 0, false, kRTFParserType_dest, "destSkip"],
"rquote" : [ "rquote", 0, false, kRTFParserType_char, "'"],
// "s" : [ "s", 0, false, kRTFParserType_dest, "destSkip"],
"header" : [ "header", 0, false, kRTFParserType_dest, "destSkip"],
"headerf" : [ "headerf", 0, false, kRTFParserType_dest, "destSkip"],
"headerl" : [ "headerl", 0, false, kRTFParserType_dest, "destSkip"],
"headerr" : [ "headerr", 0, false, kRTFParserType_dest, "destSkip"],
"info" : [ "info", 0, false, kRTFParserType_dest, "destSkip"],
"keywords" : [ "keywords", 0, false, kRTFParserType_dest, "destSkip"],
"operator" : [ "operator", 0, false, kRTFParserType_dest, "destSkip"],
"pict" : [ "pict", 0, false, kRTFParserType_dest, "destSkip"],
"printim" : [ "printim", 0, false, kRTFParserType_dest, "destSkip"],
"private1" : [ "private1", 0, false, kRTFParserType_dest, "destSkip"],
"revtim" : [ "revtim", 0, false, kRTFParserType_dest, "destSkip"],
"rxe" : [ "rxe", 0, false, kRTFParserType_dest, "destSkip"],
"stylesheet" : [ "stylesheet",0, false, kRTFParserType_dest, "destSkip"],
"subject" : [ "subject", 0, false, kRTFParserType_dest, "destSkip"],
"tc" : [ "tc", 0, false, kRTFParserType_dest, "destSkip"],
"title" : [ "title", 0, false, kRTFParserType_dest, "destSkip"],
"txe" : [ "txe", 0, false, kRTFParserType_dest, "destSkip"],
"xe" : [ "xe", 0, false, kRTFParserType_dest, "destSkip"],
"[" : [ "[", 0, false, kRTFParserType_char, '['],
" " : [ " ", 0, false, kRTFParserType_char, ' '],
"]" : [ "]", 0, false, kRTFParserType_char, ']'],
"\\" : [ "\\", 0, false, kRTFParserType_char, '\\']
}
@implementation _RTFParser : CPObject
{
CPString _codePage;
CPSize _paper;
CPString _rtf;
unsigned _curState;
CPArray _states;
unsigned _currentParseIndex;
BOOL _hexreturn;
_RTFAttribute _currentRun;
CPAttributedString _result;
CPArray _colorArray;
CPArray _fontArray;
CPString _freename;
BOOL _parsingFontTable;
}
- (id)init
{
if (self = [super init])
{
_paper = CPMakeSize(0, 0);
_rtf = "";
_curState = 0; // 0 = normal, 1 = skip
_states = [];
_currentParseIndex = 0;
_hexreturn = NO;
_currentRun = nil;
_result = [CPAttributedString new];
_colorArray = [];
_fontArray = ['Arial']; // FIXME: should be name of system font
_freename = "";
_parsingFontTable = NO;
}
return self;
}
- (CPString)_checkChar:sym parameter:ch
{
switch(_curState)
{
case 0:
if (sym && sym[4])
return sym[4];
case 1:
console.log("skipped : " + sym[4]);
return '';
default:
if (sym && sym[4])
return sym[4];
}
}
- (BOOL)pushState
{
_states.push["group"];
return YES;
}
- (BOOL)popState
{
_states.pop();
if(_curState > 0) _curState--;
return YES;
}
- (CPString)_parseSpec:sym parameter:v
{
var ch = '';
switch(sym[4])
{
case "ipfnDestSkip":
_curState++;
return '';
case "ipfnHex":
ch = _rtf.charAt(++_currentParseIndex);
var hex = '';
while(/[a-fA-F0-9\']/.test(ch))
{
if(ch == "'")
{
_currentParseIndex++;
continue;
}
hex += (ch + '');
ch = _rtf.charAt(++_currentParseIndex);
}
//ch = parseInt(ch, 16);
console.log("hex : " + hex);
_hexreturn = YES;
_currentParseIndex--;
if (_curState !== 0) return '';
else return hex;
break;
case "codePage":
ch = _rtf.charAt(++_currentParseIndex);
var code = '';
while(/[0-9]/.test(ch))
{
code += (ch + '');
ch = _rtf.charAt(++_currentParseIndex);
}
_codePage=code;
_currentParseIndex--;
break;
}
return '';
}
- (void) _flushCurrentRun
{
var newOffset = 0;
if (_currentRun)
{
if ([_result length] == _currentRun._range.location)
return;
_currentRun._range.length = [_result length] - _currentRun._range.location;
newOffset = CPMaxRange(_currentRun._range);
var dict = [_currentRun dictionary];
[_result setAttributes:dict range:_currentRun._range]; // flush previous run
}
_currentRun = [_RTFAttribute new];
_currentRun._range = CPMakeRange(newOffset, 0); // open a new one
}
- (CPString)_applyPropChange:sym parameter:param
{
console.log("prop : " + sym[0] + " / param : " + param+ ' ');
switch (sym[0])
{
case "pard":
[self _flushCurrentRun];
break;
case "b": // bold
if (param === 0)
{
if (_currentRun && _currentRun.bold)
[self _flushCurrentRun];
_currentRun.bold = NO
} else
{
if (_currentRun && !_currentRun.bold)
[self _flushCurrentRun]
_currentRun.bold = YES;
}
break;
case "i": // italic
if (param === 0)
{
if (_currentRun && _currentRun.italic)
[self _flushCurrentRun];
_currentRun.italic = NO
} else
{
if (_currentRun && !_currentRun.italic)
[self _flushCurrentRun]
_currentRun.italic = YES;
}
break;
case "qc": // paragraph center
[_currentRun.paragraph setAlignment:CPCenterTextAlignment];
break;
case "paperw":
_paper.width = param;
break;
case "paperh":
_paper.height = param;
break;
}
return '';
}
- (CPString)_changeDest:sym
{
switch (sym[0])
{
case "colortbl":
_colorArray.push([CPColor blackColor]);
break;
case "fonttbl":
_parsingFontTable = YES;
break;
}
if (sym[4] == "destSkip")
{
console.log("Dest skip start : [" + sym[0] + "]");
_curState++;
}
return '';
}
- (CPString)_translateKeyword:keyword parameter:param fParameter:(BOOL)fParam
{
if (kRgsymRtf[keyword] !== undefined ){
var sym = kRgsymRtf[keyword];
switch (sym[3])
{
case kRTFParserType_prop:
if (sym[2] || !fParam)
{
param = sym[1];
}
return [self _applyPropChange:sym parameter:param];
case kRTFParserType_char:
return [self _checkChar:sym parameter:param];
case kRTFParserType_dest:
return [self _changeDest:sym];
case kRTFParserType_spec:
return [self _parseSpec:sym parameter:param];
default:
return '';
break;
}
} else
{
switch (keyword)
{
case "red":
var oldColor = [_colorArray lastObject],
green = [oldColor greenComponent],
blue = [oldColor blueComponent];
_colorArray.pop();
_colorArray.push([CPColor colorWithRed: parseInt(param) / 255 green:green blue:blue alpha:1.0]);
break;
case "green":
var oldColor = [_colorArray lastObject],
red = [oldColor redComponent],
blue = [oldColor blueComponent];
_colorArray.pop();
_colorArray.push([CPColor colorWithRed: red green: parseInt(param) / 255 blue:blue alpha:1.0]);
break;
case "blue":
var oldColor = [_colorArray lastObject],
green = [oldColor greenComponent],
red = [oldColor redComponent];
_colorArray.pop();
_colorArray.push([CPColor colorWithRed: red green:green blue:parseInt(param) / 255 alpha:1.0]);
break;
case "cf": // change foreground color
var fontIndex = parseInt(param) - 1;
if (_currentRun && fontIndex >= 0)
_currentRun.fgColour = _colorArray[fontIndex];
break;
case "f": // change font
var fontIndex = parseInt(param);
if (_currentRun && fontIndex >= 0 && fontIndex < _fontArray.length)
_currentRun.fontName = _fontArray[fontIndex];
break;
case "fs": // change font size
_currentRun.fontSize = parseInt(param) / 2;
break;
case "tx": // tabstop
var location = parseInt(param) / 20;
if (_currentRun)
{
[_currentRun addTab:location type:CPLeftTabStopType];
}
break;
default:
console.log("skip : " + keyword + " param: " + param);
}
if (_states.length > 0) _curState = 1;
return '';
}
}
- (CPString)_parseKeyword:rtf length:len
{
var ch = '';
var fParam = false, fNeg = false;
var keyword = '';
var param = '';
_rtf = rtf;
if (++_currentParseIndex >= len)
return len;
ch = rtf.charAt(_currentParseIndex);
if (!/[a-zA-Z]/.test(ch))
{
return [self _translateKeyword:ch parameter:nil fParameter:fParam];
}
while (/[a-zA-Z]/.test(ch))
{
keyword += ch;
ch = rtf.charAt(++_currentParseIndex);
}
if( ch == '-' )
{
fNeg = true;
ch = rtf.charAt(++_currentParseIndex);
}
fParam = true;
while (/[0-9]/.test(ch))
{
param += (ch + '');
ch = rtf.charAt(++_currentParseIndex);
}
_currentParseIndex--;
param = parseInt(param);
if (fNeg)
param *= -1;
return [self _translateKeyword:keyword parameter:param fParameter:fParam];
}
- (void) _appendPlainString:(CPString) aString
{
[_result replaceCharactersInRange:CPMakeRange([_result length], 0) withString:aString];
}
- (CPAttributedString) parseRTF:(CPString)rtf
{
if(rtf.length == 0)
{
// alert("invalid rtf");
return '';
}
_currentParseIndex = -1;
var len = rtf.length;
var tmp = '';
var ch = '';
var hex = '';
var lastchar = 0;
while (_currentParseIndex < len)
{
tmp = rtf.charAt(++_currentParseIndex);
if (tmp !== "\\" && hex.length > 0)
{
[self _appendPlainString: String.fromCharCode(parseInt((hex), 16))];
hex = '';
}
switch(tmp)
{
case " ":
if (lastchar == 1)
{
lastchar = 0;
} else
{
_freename += tmp;
[self _appendPlainString:tmp];
}
break;
case "{":
if ([self pushState])
{
console.log("push");
}
break;
case "}":
if ([self popState])
{
console.log("pop");
}
if (_freename)
{
console.log(_freename);
if (_parsingFontTable)
{
_fontArray.push(_freename);
_parsingFontTable = NO;
}
_freename = "";
}
[self _flushCurrentRun]
break;
case "\\":
_freename = '';
ch = [self _parseKeyword:rtf length:len];
if (!_hexreturn && ch.length == 0)
{
lastchar = 1;
} else
{
lastchar = 0;
}
if (_hexreturn)
{
if(ch.length > 0)
{
if(parseInt(ch, 16) & 0x80)
{
hex += ch.toUpperCase();
} else
{
[self _appendPlainString: String.fromCharCode(parseInt((hex + ch), 16))];
hex = '';
}
if (hex.length == 4)
{
var temp = parseInt(hex, 16);
if (hexTable && hexTable[hex.toUpperCase()] !== undefined)
{
temp = parseInt(hexTable[hex.toUpperCase()], 16);
}
[self _appendPlainString: String.fromCharCode(temp)]
hex = '';
}
} else
{
console.log("hex skipped");
}
_hexreturn = NO;
} else
if (ch !== undefined && _curState === 0)
{
[self _appendPlainString:ch];
}
break;
case 0x0d:
case 0x0a:
case '\n':
case '\r':
break;
default:
lastchar = 0;
if (_curState == 0)
{
[self _appendPlainString:tmp];
} else if (tmp !== ';')
{
_freename += tmp;
}
break;
}
}
return _result;
}
@end
+600
View File
@@ -0,0 +1,600 @@
/*
RTFProducer.j
Serialize CPAttributedString to a RTF String
Copyright (C) 2014 Daniel Boehringer
This file is based on the RTFProducer from GNUStep
(which i co-authored with Fred Kiefer in 1999)
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
@import <Foundation/CPAttributedSting.j>
@import "CPFont.j"
@import "CPParagraphStyle.j"
@import "CPColor.j"
var PAPERSIZE = @"PaperSize";
var LEFTMARGIN = @"LeftMargin";
var RIGHTMARGIN = @"RightMargin";
var TOPMARGIN = @"TopMargin";
var BUTTOMMARGIN = @"ButtomMargin";
CPISOLatin1StringEncoding = "CPISOLatin1StringEncoding";
function _points2twips(a) { return (a)*20.0; }
@implementation RTFProducer:CPObject
{
CPAttributedString text;
CPMutableDictionary fontDict;
CPMutableDictionary colorDict;
CPDictionary docDict;
CPMutableArray attachments;
CPFont currentFont;
CPColor fgColor;
CPColor bgColor;
CPColor ulColor;
}
+ (CPString)produceRTF: (CPAttributedString) aText documentAttributes: (CPDictionary)dict
{
var mynew = [self new],
data;
return [mynew RTFDStringFromAttributedString: aText
documentAttributes: dict];
}
- (id)init
{
/*
* maintain a dictionary for the used colours
* (for rtf-header generation)
*/
colorDict = [CPMutableDictionary new];
/*
* maintain a dictionary for the used fonts
* (for rtf-header generation)
*/
fontDict = [CPMutableDictionary new];
currentFont = nil;
fgColor = [CPColor blackColor];
bgColor= [CPColor whiteColor];
return self;
}
// private stuff follows
- (CPString) fontTable
{
// write Font Table
if ([fontDict count])
{
var fontlistString = "";
var fontEnum;
var currFont;
var keyArray;
keyArray = [fontDict allKeys];
keyArray = [keyArray sortedArrayUsingSelector: @selector(compare:)];
fontEnum = [keyArray objectEnumerator];
while ((currFont = [fontEnum nextObject]) !== nil)
{
var fontFamily;
var detail;
if ([currFont isEqualToString: @"Symbol"])
fontFamily = @"tech";
else if ([currFont isEqualToString: @"Helvetica"])
fontFamily = @"swiss";
else if ([currFont isEqualToString: @"Arial"])
fontFamily = @"swiss";
else if ([currFont isEqualToString: @"Courier"])
fontFamily = @"modern";
else if ([currFont isEqualToString: @"Times"])
fontFamily = @"roman";
else fontFamily = @"nil";
detail = [CPString stringWithFormat: @"%@\\f%@ %@;",
[fontDict objectForKey: currFont], fontFamily, currFont];
fontlistString += detail;
}
return [CPString stringWithFormat: @"{\\fonttbl%@}\n", fontlistString];
}
else
return @"";
}
- (CPString) colorTable
{
// write Colour table
if ([colorDict count])
{
var result,
count = [colorDict count],
list = [CPMutableArray arrayWithCapacity: count],
keyEnum = [colorDict keyEnumerator],
next,
i;
while ((next = [keyEnum nextObject]) != nil)
{
var cn = [colorDict objectForKey: next];
[list insertObject: next atIndex: [cn intValue]-1];
}
result = [CPString stringWithString: @"{\\colortbl;"];
for (i = 0; i < count; i++)
{
var color = [[list objectAtIndex: i]
colorUsingColorSpaceName: CPCalibratedRGBColorSpace];
result += [CPString stringWithFormat:
@"\\red%d\\green%d\\blue%d;",
([color redComponent]*255),
([color greenComponent]*255),
([color blueComponent]*255)];
}
result += @"}\n";
return result;
}
else
return @"";
}
- (CPString) documentAttributes
{
if (docDict != nil)
{
var result,
detail,
val,
num,
result = [CPString string];
val = [docDict objectForKey: PAPERSIZE];
if (val != nil)
{
var size = [val sizeValue];
detail = [CPString stringWithFormat: @"\\paperw%d \\paperh%d",
_points2twips(size.width),
_points2twips(size.height)];
result += detail;
}
num = [docDict objectForKey: LEFTMARGIN];
if (num != nil)
{
var f = [num floatValue];
detail = [CPString stringWithFormat: @"\\margl%d",
_points2twips(f)];
result+= detail;
}
num = [docDict objectForKey: RIGHTMARGIN];
if (num != nil)
{
var f = [num floatValue];
detail = [CPString stringWithFormat: @"\\margr%d",
_points2twips(f)];
result += detail;
}
num = [docDict objectForKey: TOPMARGIN];
if (num != nil)
{
var f = [num floatValue];
detail = [CPString stringWithFormat: @"\\margt%d",
_points2twips(f)];
result += detail;
}
num = [docDict objectForKey: BUTTOMMARGIN];
if (num != nil)
{
var f = [num floatValue];
detail = [CPString stringWithFormat: @"\\margb%d",
_points2twips(f)];
result += detail;
}
return result;
}
else
return @"";
}
- (CPString) headerString
{
var result;
result = [CPString stringWithString: @"{\\rtf1\\ansi"];
result += [self fontTable];
result += [self colorTable];
result += [self documentAttributes];
return result;
}
- (CPString) trailerString
{
return @"}";
}
- (CPString)fontToken: (CPString) fontName
{
var fCount = [fontDict objectForKey: fontName];
if (fCount == nil)
{
var count = [fontDict count];
fCount = [CPString stringWithFormat: @"\\f%d", count];
[fontDict setObject: fCount forKey: fontName];
}
return fCount;
}
- (int)numberForColor: (CPColor)color
{
var cn,
num = [colorDict objectForKey: color];
if (num == nil)
{
cn = [colorDict count] + 1;
[colorDict setObject: [CPNumber numberWithInt: cn]
forKey: color];
}
var cn = [num intValue];
return cn + 1;
}
- (CPString) paragraphStyle: (CPParagraphStyle) paraStyle
{
var headerString = [CPString stringWithString:@"\\pard\\plain"],
twips;
if (paraStyle == nil)
return headerString;
switch ([paraStyle alignment])
{
case CPRightTextAlignment:
headerString += @"\\qr";
break;
case CPCenterTextAlignment:
headerString += @"\\qc";
break;
case CPLeftTextAlignment:
headerString += @"\\ql";
break;
case CPJustifiedTextAlignment:
headerString += @"\\qj";
break;
default:
headerString += @"\\ql";
break;
}
// write first line indent and left indent
var twips = _points2twips([paraStyle firstLineHeadIndent]);
if (twips != 0.0)
{
headerString += [CPString stringWithFormat:@"\\fi%d", twips];
}
twips = _points2twips([paraStyle headIndent]);
if (twips != 0.0)
{
headerString += [CPString stringWithFormat:@"\\li%d", twips];
}
twips = _points2twips([paraStyle tailIndent]);
if (twips != 0.0)
{
headerString += [CPString stringWithFormat:@"\\ri%d", twips];
}
twips = _points2twips([paraStyle paragraphSpacing]);
if (twips != 0.0)
{
headerString += [CPString stringWithFormat:@"\\sa%d", twips];
}
twips = _points2twips([paraStyle minimumLineHeight]);
if (twips != 0.0)
{
headerString += [CPString stringWithFormat:@"\\sl%d", twips];
}
twips = _points2twips([paraStyle maximumLineHeight]);
if (twips != 0.0)
{
headerString += [CPString stringWithFormat: @"\\sl-%d", twips];
}
// tabs
if (1)
{
var enumerator,
tab;
enumerator = [[paraStyle tabStops] objectEnumerator];
while ((tab = [enumerator nextObject]))
{
switch ([tab tabStopType])
{
case CPLeftTabStopType:
// no tabkind emission needed
break;
/* case NSRightTabStopType:
headerString += @"\\tqr";
break;
case NSCenterTabStopType:
headerString += @"\\tqc";
break;
case NSDecimalTabStopType:
headerString += @"\\tqdec";
break;
default:
NSLog(@"Unknown tab stop type.");
*/
}
headerString += [CPString stringWithFormat:@"\\tx%d",_points2twips([tab location])];
}
}
return headerString;
}
- (CPString) runStringForString: (CPString) substring
attributes: (CPDictionary) attributes
paragraphStart: (BOOL) first
{
var result = "",
headerString = "",
trailerString = "",
attribEnum,
currAttrib;
if (first)
{
var paraStyle = [attributes objectForKey:CPParagraphStyleAttributeName];
headerString += [self paragraphStyle: paraStyle];
}
/*
* analyze attributes of current run
*
* FIXME: All the character attributes should be output relative to the font
* attributes of the paragraph. So if the paragraph has underline on it should
* still be possible to switch it off for some characters, which currently is
* not possible.
*/
attribEnum = [attributes keyEnumerator];
while ((currAttrib = [attribEnum nextObject]) != nil)
{
if ([currAttrib isEqualToString: CPFontAttributeName])
{
/*
* handle fonts
*/
var font,
fontName,
traits;
font = [attributes objectForKey: CPFontAttributeName];
fontName = [font familyName];
traits = [[CPFontManager sharedFontManager] traitsOfFont: font];
/*
* font name
*/
if (currentFont == nil ||
![fontName isEqualToString: [currentFont familyName]])
{
headerString += [self fontToken: fontName];
}
/*
* font size
*/
if (currentFont == nil ||
[font size] != [currentFont size])
{
var points =[font size]*2,
pString;
pString = [CPString stringWithFormat: @"\\fs%d", points];
headerString += pString;
}
/*
* font attributes
*/
if (traits & CPItalicFontMask)
{
headerString += @"\\i";
trailerString += @"\\i0";
}
if (traits & CPBoldFontMask)
{
headerString += @"\\b";
trailerString += @"\\b0";
}
if (first)
currentFont = font;
}
else if ([currAttrib isEqualToString: CPForegroundColorAttributeName])
{
var color = [attributes objectForKey: CPForegroundColorAttributeName];
if (![color isEqual: fgColor])
{
headerString += [CPString stringWithFormat:@"\\cf%d", [self numberForColor:color]];
trailerString += @"\\cf0";
}
}
else if ([currAttrib isEqualToString: CPBackgroundColorAttributeName])
{
var color = [attributes objectForKey: CPBackgroundColorAttributeName];
if (![color isEqual: bgColor])
{
headerString += [CPString stringWithFormat:@"\\cb%d", [self numberForColor: color]];
trailerString += @"\\cb0";
}
}
else if ([currAttrib isEqualToString: CPUnderlineStyleAttributeName])
{
headerString += @"\\ul";
trailerString += @"\\ulnone";
}
else if ([currAttrib isEqualToString: CPSuperscriptAttributeName])
{
var value = [attributes objectForKey: CPSuperscriptAttributeName],
svalue = [value intValue] * 6;
if (svalue > 0)
{
headerString += [CPString stringWithFormat:@"\\up%d", svalue];
trailerString += @"\\up0";
}
else if (svalue < 0)
{
headerString +=[CPString stringWithFormat:@"\\dn-%d", svalue];
trailerString += @"\\dn0";
}
}
else if ([currAttrib isEqualToString: CPBaselineOffsetAttributeName])
{
var value = [attributes objectForKey: CPBaselineOffsetAttributeName],
svalue = [value floatValue] * 2;
if (svalue > 0)
{
headerString +=[CPString stringWithFormat:@"\\up%d", svalue];
trailerString += @"\\up0";
}
else if (svalue < 0)
{
headerString += [CPString stringWithFormat:@"\\dn-%d", svalue];
trailerString += @"\\dn0";
}
}
else if ([currAttrib isEqualToString: CPAttachmentAttributeName])
{
}
else if ([currAttrib isEqualToString: CPLigatureAttributeName])
{
}
else if ([currAttrib isEqualToString: CPKernAttributeName])
{
}
}
substring = substring.replace(/\\/g, '\\\\');
substring = substring.replace(/\n/g, '\\par\n');
substring = substring.replace(/\t/g, '\\tab');
substring = substring.replace(/{/g, '\\{');
substring = substring.replace(/}/g, '\\}');
// FIXME: All characters not in the standard encoding must be
// replaced by \'xx
if (!first)
{
var braces;
if ([headerString length])
braces = [CPString stringWithFormat: @"{%@ %@}", headerString, substring];
else
braces = substring;
result += braces;
}
else
{
var nobraces;
if ([headerString length])
nobraces = [CPString stringWithFormat: @"%@ %@", headerString, substring];
else
nobraces = substring;
result += nobraces;
}
return result + trailerString;
}
- (CPString)bodyString
{
var string = [text string],
result = "",
loc = 0,
length = [string length];
var currRange = CPMakeRange(loc, 0),
completeRange = CPMakeRange(0, length),
first = YES;
// FIXME <!> split along newline characters and run as outer loop
while (CPMaxRange(currRange) < CPMaxRange(completeRange)) // save all "runs"
{
var attributes,
substring,
runString;
attributes = [text attributesAtIndex: CPMaxRange(currRange)
longestEffectiveRange:currRange
inRange:completeRange];
substring = [string substringWithRange:currRange];
runString = [self runStringForString:substring
attributes:attributes
paragraphStart:YES];
result += runString;
first = NO;
}
return result;
}
- (CPString) RTFDStringFromAttributedString: (CPAttributedString)aText
documentAttributes: (CPDictionary)dict
{
var output = [CPString string],
headerString,
trailerString,
bodyString;
text = aText;
docDict = dict;
/*
* do not change order! (esp. body has to be generated first; builds context)
*/
bodyString = [self bodyString];
trailerString = [self trailerString];
headerString = [self headerString];
output += headerString;
output += bodyString;
output += trailerString;
return output;
}
@end