mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-08-25 04:57:03 +00:00
CPTextView commit
This commit is contained in:
@@ -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
@@ -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
@@ -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
|
||||
|
||||
Executable
+328
@@ -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
|
||||
Executable
+487
@@ -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
|
||||
Executable
+1419
File diff suppressed because it is too large
Load Diff
Executable
+186
@@ -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
|
||||
Executable
+209
@@ -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
|
||||
Executable
+295
@@ -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
|
||||
Executable
+1709
File diff suppressed because it is too large
Load Diff
Executable
+393
@@ -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
|
||||
Executable
+700
@@ -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
|
||||
Executable
+600
@@ -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
|
||||
Reference in New Issue
Block a user