Files
cappuccino/AppKit/CPTextView/CPTypesetter.j
T
Alexandre Wilhelm 075da34cdb Fixed: circular import with CPText
Previously CPText was in CPTextView.j, now it is in CPText.j.
It fixes some other circular import as well, fixes some global var to import.
Fixed some code style.
2014-06-03 21:05:04 -07:00

412 lines
13 KiB
Plaintext
Executable File

/*
* 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 "CPParagraphStyle.j"
@import "CPTextStorage.j"
/*
CPTypesetterControlCharacterAction
*/
CPTypesetterZeroAdvancementAction = (1 << 0);
CPTypesetterWhitespaceAction = (1 << 1);
CPSTypesetterHorizontalTabAction = (1 << 2);
CPTypesetterLineBreakAction = (1 << 3);
CPTypesetterParagraphBreakAction = (1 << 4);
CPTypesetterContainerBreakAction = (1 << 5);
var _measuringContext,
_measuringContextFont,
_isCanvasSizingInvalid,
_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:(UIntegerReference)nextGlyph
{
_CPRaiseInvalidAbstractInvocation(self, _cmd);
}
@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:(CGPoint)lineOrigin
currentContainerSize:(CGSize)containerSize
advancements:(CPArray)advancements
lineCount:(unsigned)lineCount
{
[_layoutManager setTextContainer:_currentTextContainer forGlyphRange:lineRange]; // creates a new lineFragment
var rect = CGRectMake(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 = CGPointCreateCopy([_layoutManager lineFragmentRectForGlyphAtIndex:glyphIndex effectiveRange:nil].origin);
else if ([_layoutManager extraLineFragmentTextContainer])
lineOrigin = CGPointMake(0, [_layoutManager extraLineFragmentUsedRect].origin.y);
else
lineOrigin = CGPointMake(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] || [CPFont systemFontOfSize:12.0];
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':
case '\r':
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; // start the line starts directly at current character
}
_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)
{
_indexOfCurrentContainer++;
_indexOfCurrentContainer = MAX(_indexOfCurrentContainer, [[_layoutManager textContainers] count] - 1);
_currentTextContainer = [[_layoutManager textContainers] objectAtIndex: _indexOfCurrentContainer];
}
lineOrigin.x = 0;
numLines++;
isNewline = NO;
}
_lineWidth = 0;
advancements = [];
currentAnchor = 0;
prevRangeWidth = 0;
_lineHeight = 0;
_lineBase = 0;
lineRange = CPMakeRange(glyphIndex + 1, 0);
measuringRange = 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 = CGRectMake(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