API  1.0.0
CPLayoutManager.j
Go to the documentation of this file.
1 /*
2  * CPLayoutManager.j
3  * AppKit
4  *
5  * FIXME remove from DOM when scrolled out of visible area? (as done in CPTableView)
6  *
7  *
8  * Created by Daniel Boehringer on 27/12/2013.
9  * All modifications copyright Daniel Boehringer 2013.
10  * Extensive code formatting and review by Andrew Hankinson
11  * Based on original work by
12  * Emmanuel Maillard on 27/02/2010.
13  * Copyright Emmanuel Maillard 2010.
14  *
15  * This library is free software; you can redistribute it and/or
16  * modify it under the terms of the GNU Lesser General Public
17  * License as published by the Free Software Foundation; either
18  * version 2.1 of the License, or (at your option) any later version.
19  *
20  * This library is distributed in the hope that it will be useful,
21  * but WITHOUT ANY WARRANTY; without even the implied warranty of
22  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
23  * Lesser General Public License for more details.
24  *
25  * You should have received a copy of the GNU Lesser General Public
26  * License along with this library; if not, write to the Free Software
27  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
28  */
29 
30 
31 @global _MakeRangeFromAbs
32 
33 
34 function _isNewlineCharacter(chr)
35 {
36  return (chr === '\n' || chr === '\r');
37 }
38 
39 function _RectEqualToRectHorizontally(lhsRect, rhsRect)
40 {
41  return (lhsRect.origin.x == rhsRect.origin.x &&
42  lhsRect.size.width == rhsRect.size.width &&
43  lhsRect.size.height == rhsRect.size.height);
44 }
45 
46 _oncontextmenuhandler = function () { return false; };
47 
48 
52 @implementation CPLayoutManager : CPObject
53 {
54  Class _lineFragmentFactory;
55  CPMutableArray _textContainers;
56  CPTextStorage _textStorage;
57  CPTypesetter _typesetter;
58 
59  CPMutableArray _lineFragments;
60  CPMutableArray _lineFragmentsForRescue;
61  id _extraLineFragment;
62 
63  CPMutableArray _temporaryAttributes;
64 
65  BOOL _isValidatingLayoutAndGlyphs;
66  CPRange _removeInvalidLineFragmentsRange;
67 }
68 
69 
70 #pragma mark -
71 #pragma mark Init methods
72 
73 - (id)init
74 {
75  if (self = [super init])
76  {
77  [self _init];
78  }
79 
80  return self;
81 }
82 
83 - (void)_init
84 {
85  _isValidatingLayoutAndGlyphs = NO;
86  _lineFragmentFactory = [_CPLineFragment class];
87  _lineFragments = [[CPMutableArray alloc] init];
88  _textContainers = [[CPMutableArray alloc] init];
89  _textStorage = [[CPTextStorage alloc] init];
90  _typesetter = [CPTypesetter sharedSystemTypesetter];
91 
92  [_textStorage addLayoutManager:self];
93 }
94 
95 
96 #pragma mark -
97 #pragma mark Text containes method
98 
99 - (void)insertTextContainer:(CPTextContainer)aContainer atIndex:(int)index
100 {
101  [_textContainers insertObject:aContainer atIndex:index];
102  [aContainer setLayoutManager:self];
103 }
104 
105 - (void)addTextContainer:(CPTextContainer)aContainer
106 {
107  [_textContainers addObject:aContainer];
108  [aContainer setLayoutManager:self];
109 }
110 
111 - (void)removeTextContainerAtIndex:(int)index
112 {
113  var container = [_textContainers objectAtIndex:index];
114  [container setLayoutManager:nil];
115  [_textContainers removeObjectAtIndex:index];
116 }
117 
118 // <!> fixme
119 - (int)numberOfGlyphs
120 {
121  return [_textStorage length];
122 }
123 
124 - (int)numberOfCharacters
125 {
126  return [_textStorage length];
127 }
128 
129 - (CPTextView)firstTextView
130 {
131  return [_textContainers[0] textView];
132 }
133 
134 // from cocoa (?)
135 - (CPTextView)textViewForBeginningOfSelection
136 {
137  return [[_textContainers objectAtIndex:0] textView];
138 }
139 
140 - (BOOL)layoutManagerOwnsFirstResponderInWindow:(CPWindow)aWindow
141 {
142  var firstResponder = [aWindow firstResponder],
143  c = [_textContainers count];
144 
145  for (var i = 0; i < c; i++)
146  {
147  if ([_textContainers[i] textView] === firstResponder)
148  return YES;
149  }
150 
151  return NO;
152 }
153 
154 - (CGRect)boundingRectForGlyphRange:(CGRange)aRange inTextContainer:(CPTextContainer)container
155 {
156  if (![self numberOfGlyphs])
157  return CGRectMake(0, 0, 1, 12); // crude hack to give a cursor in an empty doc.
158 
159  if (CPMaxRange(aRange) > [self numberOfGlyphs])
160  aRange = CPMakeRange([self numberOfGlyphs] - 1, 1);
161 
162  var fragments = _objectsInRange(_lineFragments, aRange),
163  rect = nil,
164  c = [fragments count];
165 
166  for (var i = 0; i < c; i++)
167  {
168  var fragment = fragments[i];
169 
170  if (fragment._textContainer === container)
171  {
172  var frames = [fragment glyphFrames],
173  l = frames ? frames.length : 0;
174 
175  for (var j = 0; j < l; j++)
176  {
177  if (CPLocationInRange(fragment._range.location + j, aRange))
178  {
179  if (!rect)
180  rect = CGRectCreateCopy(frames[j]);
181  else
182  rect = CGRectUnion(rect, frames[j]);
183  }
184  }
185  }
186  }
187 
188  return rect ? rect : CGRectMakeZero();
189 }
190 
191 - (CPRange)glyphRangeForTextContainer:(CPTextContainer)aTextContainer
192 {
193  var range = nil,
194  c = [_lineFragments count];
195 
196  for (var i = 0; i < c; i++)
197  {
198  var fragment = _lineFragments[i];
199 
200  if (fragment._textContainer === aTextContainer)
201  {
202  if (!range)
203  range = CPMakeRangeCopy(fragment._range);
204  else
205  range = CPUnionRange(range, fragment._range);
206  }
207  }
208 
209  return range ? range : CPMakeRange(CPNotFound, 0);
210 }
211 
212 - (void)_removeInvalidLineFragments
213 {
214  _lineFragmentsForRescue = [_lineFragments copy];
215  [_lineFragmentsForRescue makeObjectsPerformSelector:@selector(_deinvalidate)];
216 
217  if (_removeInvalidLineFragmentsRange && _removeInvalidLineFragmentsRange.length && _lineFragments.length)
218  {
219  // [[_lineFragments subarrayWithRange:_removeInvalidLineFragmentsRange] makeObjectsPerformSelector:@selector(invalidate)];
220  [_lineFragments removeObjectsInRange:_removeInvalidLineFragmentsRange];
221  [[_lineFragmentsForRescue subarrayWithRange:_removeInvalidLineFragmentsRange] makeObjectsPerformSelector:@selector(invalidate)];
222  }
223 
224 }
225 
226 - (void)_cleanUpDOM
227 {
228  var l = _lineFragmentsForRescue? _lineFragmentsForRescue.length : 0;
229 
230  for (var i = 0; i < l; i++)
231  {
232  if (_lineFragmentsForRescue[i]._isInvalid)
233  [_lineFragmentsForRescue[i] _removeFromDOM];
234  }
235 }
236 
237 - (void)_validateLayoutAndGlyphs
238 {
239  if (_isValidatingLayoutAndGlyphs)
240  return;
241 
242  _isValidatingLayoutAndGlyphs = YES;
243 
244  var startIndex = CPNotFound,
245  removeRange = CPMakeRange(0, 0),
246  l = _lineFragments.length;
247 
248  if (l)
249  {
250  for (var i = 0; i < l; i++)
251  {
252  if (_lineFragments[i]._isInvalid)
253  {
254  startIndex = _lineFragments[i]._range.location;
255  removeRange.location = i;
256  removeRange.length = l - i;
257  break;
258  }
259  }
260 
261  // start one line above current line to make sure that a word can jump up
262  if (startIndex == CPNotFound && CPMaxRange (_lineFragments[l - 1]._range) < [_textStorage length])
263  startIndex = CPMaxRange(_lineFragments[l - 1]._range);
264  }
265  else
266  {
267  startIndex = 0;
268  }
269 
270  /* nothing to validate and layout */
271  if (startIndex == CPNotFound)
272  {
273  _isValidatingLayoutAndGlyphs = NO;
274  return;
275  }
276 
277  if (removeRange.length)
278  _removeInvalidLineFragmentsRange = CPMakeRangeCopy(removeRange);
279 
280  // We erased all lines
281  if (!startIndex)
282  [self setExtraLineFragmentRect:CGRectMake(0, 0) usedRect:CGRectMake(0, 0) textContainer:nil];
283  // document.title=startIndex;
284 
285  [_typesetter layoutGlyphsInLayoutManager:self startingAtGlyphIndex:startIndex maxNumberOfLineFragments:-1 nextGlyphIndex:nil];
286 
287 #if PLATFORM(DOM)
288  [self _cleanUpDOM];
289 #endif
290 
291  _isValidatingLayoutAndGlyphs = NO;
292 }
293 
294 - (BOOL)_rescuingInvalidFragmentsWasPossibleForGlyphRange:(CPRange)aRange
295 {
296  var l = _lineFragments.length,
297  location = aRange.location,
298  found = NO,
299  targetLine = 0;
300 
301  // try to find the first linefragment of the desired range
302  for (; targetLine < l; targetLine++)
303  {
304  if (CPLocationInRange(location, _lineFragments[targetLine]._range))
305  {
306  found = YES;
307  break;
308  }
309  }
310 
311  if (!found)
312  return NO;
313 
314  if (!_lineFragmentsForRescue[targetLine])
315  return NO;
316 
317  var startLineForDOMRemoval = targetLine,
318  isIdentical = YES,
319  newLineFragment= _lineFragments[targetLine],
320  oldLineFragment = _lineFragmentsForRescue[targetLine],
321  oldLength = CPMaxRange([_lineFragmentsForRescue lastObject]._range),
322  newLength = [[_textStorage string].length],
323  removalSkip = 1;
324 
325  // if (ABS(newLength - oldLength) > 1)
326  // return NO;
327 
328  if (![oldLineFragment isVisuallyIdenticalToFragment:newLineFragment])
329  {
330  isIdentical = NO;
331 
332  // deleting newline in its own line-> move up instead of re-layouting
333  if (newLength < oldLength && oldLineFragment._range.length == 1 && newLineFragment._range.length > 1 && newLineFragment._range.location === oldLineFragment._range.location && oldLineFragment._isLast)
334  {
335  isIdentical = YES;
336  targetLine--;
337  removalSkip++;
338  }
339 
340  // newline entered in its own line-> move down instead of re-layouting
341  if (newLength > oldLength && newLineFragment._range.length == 1 && oldLineFragment._range.length > 1 && newLineFragment._range.location === oldLineFragment._range.location && newLineFragment._isLast)
342  {
343  isIdentical = YES;
344  startLineForDOMRemoval--;
345  }
346  }
347 
348  // patch the linefragments instead of re-layoutung
349  if (isIdentical)
350  {
351  var rangeOffset = CPMaxRange(_lineFragments[targetLine]._range) - CPMaxRange(_lineFragmentsForRescue[startLineForDOMRemoval]._range);
352 
353  if (ABS(rangeOffset) !== ABS(newLength - oldLength))
354  return NO;
355 
356  var verticalOffset = _lineFragments[targetLine]._fragmentRect.origin.y - _lineFragmentsForRescue[startLineForDOMRemoval]._fragmentRect.origin.y,
357  l = _lineFragmentsForRescue.length,
358  newTargetLine = startLineForDOMRemoval + removalSkip;
359 
360  for (; newTargetLine < l; newTargetLine++)
361  {
362  _lineFragmentsForRescue[newTargetLine]._isInvalid = NO; // protect them from final removal
363  [_lineFragmentsForRescue[newTargetLine] _relocateVerticallyByY:verticalOffset rangeOffset:rangeOffset];
364  _lineFragments.push(_lineFragmentsForRescue[newTargetLine]);
365  }
366  }
367 
368  return isIdentical;
369 }
370 
371 - (void)invalidateDisplayForGlyphRange:(CPRange)range
372 {
373  var lineFragments = _objectsInRange(_lineFragments, range);
374 
375  for (var i = 0; i < lineFragments.length; i++)
376  [[lineFragments[i]._textContainer textView] setNeedsDisplayInRect:lineFragments[i]._fragmentRect];
377 }
378 
379 - (void)invalidateLayoutForCharacterRange:(CPRange)aRange isSoft:(BOOL)flag actualCharacterRange:(CPRangePointer)actualCharRange
380 {
381  var firstFragmentIndex = _lineFragments.length ? [_lineFragments _indexOfObject: aRange.location sortedByFunction:_sortRange context:nil] : CPNotFound;
382 
383  if (firstFragmentIndex == CPNotFound)
384  {
385  if (_lineFragments.length)
386  {
387  firstFragmentIndex = _lineFragments.length - 1;
388  }
389  else
390  {
391  if (actualCharRange)
392  {
393  actualCharRange.length = aRange.length;
394  actualCharRange.location = 0;
395  }
396 
397  return;
398  }
399  }
400  else
401  {
402  firstFragmentIndex = firstFragmentIndex + (firstFragmentIndex ? -1 : 0);
403  }
404 
405  var fragment = _lineFragments[firstFragmentIndex],
406  range = CPMakeRangeCopy(fragment._range);
407 
408  fragment._isInvalid = YES;
409 
410  /* invalidated all fragments that follow */
411  for (var i = firstFragmentIndex + 1; i < _lineFragments.length; i++)
412  {
413  _lineFragments[i]._isInvalid = YES;
414  range = CPUnionRange(range, _lineFragments[i]._range);
415  }
416 
417  if (CPMaxRange(range) < CPMaxRange(aRange))
418  range = CPUnionRange(range, aRange);
419 
420  if (actualCharRange)
421  {
422  actualCharRange.length = range.length;
423  actualCharRange.location = range.location;
424  }
425 }
426 
427 - (void)textStorage:(CPTextStorage)textStorage edited:(unsigned)mask range:(CPRange)charRange changeInLength:(int)delta invalidatedRange:(CPRange)invalidatedRange
428 {
429  var actualRange = CPMakeRange(CPNotFound,0);
430 
431  [self invalidateLayoutForCharacterRange:invalidatedRange isSoft:NO actualCharacterRange:actualRange];
432  [self invalidateDisplayForGlyphRange:actualRange];
433  [self _validateLayoutAndGlyphs];
434  [[self firstTextView] sizeToFit];
435 }
436 
437 - (CPRange)glyphRangeForBoundingRect:(CGRect)aRect inTextContainer:(CPTextContainer)container
438 {
439  var c = [_lineFragments count],
440  range;
441 
442  for (var i = 0; i < c; i++)
443  {
444  var fragment = _lineFragments[i];
445 
446  if (fragment._textContainer === container)
447  {
448  if (CGRectContainsRect(aRect, fragment._fragmentRect))
449  {
450  if (!range)
451  range = CPMakeRangeCopy(fragment._range);
452  else
453  range = CPUnionRange(range, fragment._range);
454  }
455  else
456  {
457  var glyphRange = CPMakeRange(CPNotFound, 0),
458  frames = [fragment glyphFrames];
459 
460  for (var j = 0; j < frames.length; j++)
461  {
462  if (CGRectIntersectsRect(aRect, frames[j]))
463  {
464  if (glyphRange.location == CPNotFound)
465  glyphRange.location = fragment._range.location + j;
466  else
467  glyphRange.length++;
468  }
469  }
470 
471  if (glyphRange.location != CPNotFound)
472  {
473  if (!range)
474  range = CPMakeRangeCopy(glyphRange);
475  else
476  range = CPUnionRange(range, glyphRange);
477  }
478  }
479  }
480  }
481 
482  return range ? range : CPMakeRange(0,0);
483 }
484 
485 - (void)drawBackgroundForGlyphRange:(CPRange)aRange atPoint:(CGPoint)aPoint
486 {
487 
488 }
489 
490 - (void)drawUnderlineForGlyphRange:(CPRange)glyphRange
491  underlineType:(int)underlineVal
492  baselineOffset:(float)baselineOffset
493  lineFragmentRect:(CGRect)lineFragmentRect
494  lineFragmentGlyphRange:(CPRange)lineGlyphRange
495  containerOrigin:(CGPoint)containerOrigin
496 {
497 // FIXME
498 }
499 
500 - (void)drawGlyphsForGlyphRange:(CPRange)aRange atPoint:(CGPoint)aPoint
501 {
502  var lineFragments = _objectsInRange(_lineFragments, aRange);
503 
504  if (!lineFragments.length)
505  return;
506 
507  var paintedRange = CPMakeRangeCopy(aRange),
508  l = lineFragments.length,
509  lineFragmentIndex,
510  ctx;
511 
512  for (lineFragmentIndex = 0; lineFragmentIndex < l; lineFragmentIndex++)
513  {
514  var currentFragment = lineFragments[lineFragmentIndex];
515  [currentFragment drawInContext:ctx atPoint:aPoint forRange:paintedRange];
516  }
517 }
518 
519 - (unsigned)glyphIndexForPoint:(CGPoint)point inTextContainer:(CPTextContainer)container fractionOfDistanceThroughGlyph:(FloatArray)partialFraction
520 {
521  var c = [_lineFragments count];
522 
523  for (var i = 0; i < c; i++)
524  {
525  var fragment = _lineFragments[i];
526 
527  if (fragment._textContainer === container)
528  {
529  var frames = [fragment glyphFrames],
530  len = fragment._range.length;
531 
532  for (var j = 0; j < len; j++)
533  {
534  if (CGRectContainsPoint(frames[j], point))
535  {
536  if (partialFraction)
537  partialFraction[0] = (point.x - frames[j].origin.x) / frames[j].size.width;
538 
539  return fragment._range.location + j;
540  }
541  }
542  }
543  }
544 
545  // Not found, maybe a point left to the last character was clicked -> search again with broader constraints
546  if ([[_textStorage string] length])
547  {
548  for (var i = 0; i < c; i++)
549  {
550  var fragment = _lineFragments[i];
551 
552  if (fragment._textContainer === container)
553  {
554  // Within the horizontal territory of the current (not-empty) line?
555  if (fragment._range.length > 0 && point.y > fragment._fragmentRect.origin.y &&
556  point.y <= fragment._fragmentRect.origin.y + fragment._fragmentRect.size.height)
557  {
558  // Skip tabs and move on the last fragment in this line
559  if (i < c - 1 && _lineFragments[i + 1]._fragmentRect.origin.y === fragment._fragmentRect.origin.y)
560  continue;
561 
562  var nlLoc = CPMaxRange(fragment._range),
563  lastFrame = [fragment glyphFrames][fragment._range.length - 1],
564  firstFrame = [fragment glyphFrames][0];
565 
566  // stay on the line the newline character belongs to
567  if (_isNewlineCharacter([[_textStorage string] characterAtIndex:nlLoc > 0 ? nlLoc - 1 : 0]))
568  nlLoc--;
569 
570  // Clicked right to the last character
571  if (point.x > CGRectGetMaxX(lastFrame))
572  return nlLoc;
573  // Clicked left to the last character
574  else if (point.x <= CGRectGetMinX(firstFrame))
575  return fragment._range.location;
576  else
577  return nlLoc;
578  }
579  }
580  }
581  }
582 
583  return point.y > 0 ? [[_textStorage string] length] : 0;
584 }
585 
586 - (unsigned)glyphIndexForPoint:(CGPoint)point inTextContainer:(CPTextContainer)container
587 {
588  return [self glyphIndexForPoint:point inTextContainer:container fractionOfDistanceThroughGlyph:nil];
589 }
590 
591 - (void)_setAttributes:(CPDictionary)attributes toTemporaryAttributes:(_CPTemporaryAttributes)tempAttributes
592 {
593  tempAttributes._attributes = attributes;
594 }
595 
596 - (void)_addAttributes:(CPDictionary)attributes toTemporaryAttributes:(_CPTemporaryAttributes)tempAttributes
597 {
598  [tempAttributes._attributes addEntriesFromDictionary:attributes];
599 }
600 
601 - (void)_handleTemporaryAttributes:(CPDictionary)attributes forCharacterRange:(CPRange)charRange withSelector:(SEL)attributesOperation
602 {
603  // FIXME
604 }
605 
606 - (void)setTemporaryAttributes:(CPDictionary)attributes forCharacterRange:(CPRange)charRange
607 {
608  [self _handleTemporaryAttributes:attributes forCharacterRange:charRange withSelector:@selector(_setAttributes:toTemporaryAttributes:)];
609 }
610 
611 - (void)addTemporaryAttributes:(CPDictionary)attributes forCharacterRange:(CPRange)charRange
612 {
613  [self _handleTemporaryAttributes:attributes forCharacterRange:charRange withSelector:@selector(_addAttributes:toTemporaryAttributes:)];
614 }
615 
616 - (void)removeTemporaryAttribute:(CPString)attributeName forCharacterRange:(CPRange)charRange
617 {
618  // FIXME
619 }
620 
621 - (CPDictionary)temporaryAttributesAtCharacterIndex:(unsigned)index effectiveRange:(CPRangePointer)effectiveRange
622 {
623  // FIXME
624 }
625 
626 - (void)textContainerChangedTextView:(CPTextContainer)aContainer
627 {
628  // FIXME
629 }
630 
631 - (void)_appendNewLineFragmentInTextContainer:(CPTextContainer)aTextContainer forGlyphRange:(CPRange)glyphRange
632 {
633  _lineFragments.push([[_lineFragmentFactory alloc] initWithRange:glyphRange textContainer:aTextContainer textStorage:_textStorage]);
634 }
635 
636 - (void)setTextContainer:(CPTextContainer)aTextContainer forGlyphRange:(CPRange)glyphRange
637 {
638  var fragments = _objectsInRange(_lineFragments, glyphRange),
639  l = fragments.length;
640 
641  for (var i = 0; i < l; i++)
642  fragments[i]._textContainer = aTextContainer;
643 }
644 
645 - (id)_lineFragmentForLocation:(unsigned) aLoc
646 {
647  var fragments = _objectsInRange(_lineFragments, CPMakeRange(aLoc, 0)),
648  l = fragments.length;
649 
650  if (l > 0)
651  return fragments[0];
652 
653  return nil;
654 }
655 
656 - (id)_firstLineFragmentForLineFromLocation:(unsigned)location
657 {
658  var l = _lineFragments.length;
659 
660  for (var i = 0; i < l; i++)
661  {
662  if (CPLocationInRange(location, _lineFragments[i]._range))
663  {
664  var j = i;
665 
666  while (--j > 0 && !_lineFragments[j]._isLast)
667  {
668  // body intentionally left empty
669  }
670 
671  return _lineFragments[j + 1];
672  }
673  }
674 
675  return nil;
676 }
677 - (id)_lastLineFragmentForLineFromLocation:(unsigned)location
678 {
679  var l = _lineFragments.length;
680 
681  for (var i = 0; i < l; i++)
682  {
683  if (CPLocationInRange(location, _lineFragments[i]._range))
684  {
685  var j = i;
686 
687  while (!_lineFragments[j]._isLast)
688  j++;
689 
690  return _lineFragments[j];
691  }
692  }
693 
694  return nil;
695 }
696 
697 - (double)_characterOffsetAtLocation:(unsigned)location
698 {
699  var lineFragment = _objectWithLocationInRange(_lineFragments, location);
700 
701  if (!lineFragment)
702  return 0.0;
703 
704  var index = location - lineFragment._range.location;
705 
706  return lineFragment._glyphsOffsets[index];
707 }
708 
709 - (double)_descentAtLocation:(unsigned)location
710 {
711  var lineFragment = _objectWithLocationInRange(_lineFragments, location);
712 
713  if (!lineFragment)
714  return 0.0;
715 
716  var index = location - lineFragment._range.location;
717 
718  return lineFragment._glyphsFrames[index]._descent;
719 }
720 
721 - (void)setLineFragmentRect:(CGRect)fragmentRect forGlyphRange:(CPRange)glyphRange usedRect:(CGRect)usedRect
722 {
723  var lineFragment = _objectWithLocationInRange(_lineFragments, glyphRange.location);
724 
725  if (lineFragment)
726  {
727  lineFragment._fragmentRect = CGRectCreateCopy(fragmentRect);
728  lineFragment._usedRect = CGRectCreateCopy(usedRect);
729  }
730 }
731 
732 - (void)_setAdvancements:(CPArray)someAdvancements forGlyphRange:(CPRange)glyphRange
733 {
734  var lineFragment = _objectWithLocationInRange(_lineFragments, glyphRange.location);
735 
736  if (lineFragment)
737  [lineFragment setAdvancements:someAdvancements];
738 }
739 
740 - (void)setLocation:(CGPoint)aPoint forStartOfGlyphRange:(CPRange)glyphRange
741 {
742  var lineFragment = _objectWithLocationInRange(_lineFragments, glyphRange.location);
743 
744  if (lineFragment)
745  lineFragment._location = CGPointCreateCopy(aPoint);
746 }
747 
748 - (CGRect)extraLineFragmentRect
749 {
750  if (_extraLineFragment)
751  return CGRectCreateCopy(_extraLineFragment._fragmentRect);
752 
753  return CGRectMakeZero();
754 }
755 
756 - (CPTextContainer)extraLineFragmentTextContainer
757 {
758  if (_extraLineFragment)
759  return _extraLineFragment._textContainer;
760 
761  return nil;
762 }
763 
764 - (CGRect)extraLineFragmentUsedRect
765 {
766  if (_extraLineFragment)
767  return CGRectCreateCopy(_extraLineFragment._usedRect);
768 
769  return CGRectMakeZero();
770 }
771 
772 - (void)setExtraLineFragmentRect:(CGRect)rect usedRect:(CGRect)usedRect textContainer:(CPTextContainer)textContainer
773 {
774  if (textContainer)
775  {
776  _extraLineFragment = {};
777  _extraLineFragment._fragmentRect = CGRectCreateCopy(rect);
778  _extraLineFragment._usedRect = CGRectCreateCopy(usedRect);
779  _extraLineFragment._textContainer = textContainer;
780  }
781  else
782  {
783  _extraLineFragment = nil;
784  }
785 }
786 
787 - (CGRect)usedRectForTextContainer:(CPTextContainer)textContainer
788 {
789  var rect,
790  l = _lineFragments.length;
791 
792  for (var i = 0; i < l; i++)
793  {
794  if (_lineFragments[i]._textContainer === textContainer)
795  {
796  if (rect)
797  rect = CGRectUnion(rect, _lineFragments[i]._usedRect);
798  else
799  rect = CGRectCreateCopy(_lineFragments[i]._usedRect);
800  }
801  }
802 
803  return rect ? rect : CGRectMakeZero();
804 }
805 
806 - (CGRect)lineFragmentRectForGlyphAtIndex:(unsigned)glyphIndex effectiveRange:(CPRangePointer)effectiveGlyphRange
807 {
808  var lineFragment = _objectWithLocationInRange(_lineFragments, glyphIndex);
809 
810  if (!lineFragment)
811  return CGRectMakeZero();
812 
813  if (effectiveGlyphRange)
814  {
815  effectiveGlyphRange.location = lineFragment._range.location;
816  effectiveGlyphRange.length = lineFragment._range.length;
817  }
818 
819  return CGRectCreateCopy(lineFragment._fragmentRect);
820 }
821 
822 - (CGRect)lineFragmentUsedRectForGlyphAtIndex:(unsigned)glyphIndex effectiveRange:(CPRangePointer)effectiveGlyphRange
823 {
824  var lineFragment = _objectWithLocationInRange(_lineFragments, glyphIndex);
825 
826  if (!lineFragment)
827  return CGRectMakeZero();
828 
829  if (effectiveGlyphRange)
830  {
831  effectiveGlyphRange.location = lineFragment._range.location;
832  effectiveGlyphRange.length = lineFragment._range.length;
833  }
834 
835  return CGRectCreateCopy(lineFragment._usedRect);
836 }
837 
838 - (CGPoint)locationForGlyphAtIndex:(unsigned)index
839 {
840  if (_lineFragments.length > 0 && index >= [self numberOfGlyphs] - 1)
841  {
842  var lineFragment= _lineFragments[_lineFragments.length - 1],
843  glyphFrames = [lineFragment glyphFrames];
844 
845  if (glyphFrames.length > 0)
846  return CGPointCreateCopy(glyphFrames[glyphFrames.length - 1].origin);
847  }
848 
849  var lineFragment = _objectWithLocationInRange(_lineFragments, index);
850 
851  if (lineFragment)
852  {
853  if (index == lineFragment._range.location)
854  return CGPointCreateCopy(lineFragment._location);
855 
856  var glyphFrames = [lineFragment glyphFrames];
857 
858  return CGPointCreateCopy(glyphFrames[index - lineFragment._range.location].origin);
859  }
860 
861  return CGPointMakeZero();
862 }
863 
864 - (CPTextContainer)textContainerForGlyphAtIndex:(unsigned)index effectiveRange:(CPRangePointer)effectiveGlyphRange withoutAdditionalLayout:(BOOL)flag
865 {
866  var lineFragment = _objectWithLocationInRange(_lineFragments, index);
867 
868  if (lineFragment)
869  {
870  if (effectiveGlyphRange)
871  {
872  effectiveGlyphRange.location = lineFragment._range.location;
873  effectiveGlyphRange.length = lineFragment._range.length;
874  }
875 
876  return lineFragment._textContainer;
877  }
878 
879  return [_textContainers lastObject];
880 }
881 
882 - (CPTextContainer)textContainerForGlyphAtIndex:(unsigned)index effectiveRange:(CPRangePointer)effectiveGlyphRange
883 {
884  return [self textContainerForGlyphAtIndex:index effectiveRange:effectiveGlyphRange withoutAdditionalLayout:NO];
885 }
886 
887 - (CPRange)characterRangeForGlyphRange:(CPRange)aRange actualGlyphRange:(CPRangePointer)actualRange
888 {
889  return _MakeRangeFromAbs([self characterIndexForGlyphAtIndex:aRange.location],
890  [self characterIndexForGlyphAtIndex:CPMaxRange(aRange)]);
891 }
892 
893 - (unsigned)characterIndexForGlyphAtIndex:(unsigned)index
894 {
895  /* FIXME: stub */
896  return index;
897 }
898 
899 - (CPArray)rectArrayForCharacterRange:(CPRange)charRange
900  withinSelectedCharacterRange:(CPRange)selectedCharRange
901  inTextContainer:(CPTextContainer)container
902  rectCount:(CGRectPointer)rectCount
903 {
904 
905  var rectArray = [],
906  lineFragments = _objectsInRange(_lineFragments, selectedCharRange);
907 
908  if (!lineFragments.length)
909  return rectArray;
910 
911  var containerSize = [container containerSize];
912 
913  for (var i = 0; i < lineFragments.length; i++)
914  {
915  var fragment = lineFragments[i];
916 
917  if (fragment._textContainer === container)
918  {
919  var frames = [fragment glyphFrames],
920  rect = nil,
921  len = fragment._range.length;
922 
923  for (var j = 0; j < len; j++)
924  {
925  if (CPLocationInRange(fragment._range.location + j, selectedCharRange))
926  {
927  var correctedRect = CGRectCreateCopy(frames[j]);
928  correctedRect.size.height -= frames[j]._descent;
929  correctedRect.origin.y -= frames[j]._descent;
930  if (!rect)
931  rect = CGRectCreateCopy(correctedRect);
932  else
933  rect = CGRectUnion(rect, correctedRect);
934 
935  if (_isNewlineCharacter([[_textStorage string] characterAtIndex:MAX(0, CPMaxRange(selectedCharRange) - 1)]))
936  {
937  rect.size.width = containerSize.width - rect.origin.x;
938  }
939  }
940  }
941 
942  if (rect)
943  rectArray.push(rect);
944  }
945  }
946 
947  var len = rectArray.length;
948 
949  for (var i = 0; i < len - 1; i++) // extend the width of all but the last one
950  {
951  if (FLOOR(CGRectGetMaxY(rectArray[i])) == FLOOR(CGRectGetMaxY(rectArray[i + 1])))
952  continue;
953 
954  rectArray[i].size.width = containerSize.width - rectArray[i].origin.x;
955  }
956 
957  return rectArray;
958 }
959 
960 @end
961 
962 
963 var CPLayoutManagerTextStorageKey = @"CPLayoutManagerTextStorageKey";
964 
966 
967 - (id)initWithCoder:(CPCoder)aCoder
968 {
969  self = [super init];
970 
971  if (self)
972  {
973  [self _init];
974 
975  _textStorage = [aCoder decodeObjectForKey:CPLayoutManagerTextStorageKey];
976  [_textStorage addLayoutManager:self];
977  }
978 
979  return self;
980 }
981 
982 - (void)encodeWithCoder:(CPCoder)aCoder
983 {
984  [aCoder encodeObject:_textStorage forKey:CPLayoutManagerTextStorageKey];
985 }
986 
987 @end
988 
989 
991 
992 - (unsigned)_indexOfObject:(id)anObject sortedByFunction:(Function)aFunction context:(id)aContext
993 {
994  var length= self.length;
995 
996  if (!aFunction)
997  return CPNotFound;
998 
999  if (length === 0)
1000  return -1;
1001 
1002  var mid,
1003  c,
1004  first = 0,
1005  last = length - 1;
1006 
1007  while (first <= last)
1008  {
1009  mid = FLOOR((first + last) / 2);
1010  c = aFunction(anObject, self[mid], aContext);
1011 
1012  if (c > 0)
1013  {
1014  first = mid + 1;
1015  }
1016  else if (c < 0)
1017  {
1018  last = mid - 1;
1019  }
1020  else
1021  {
1022  while (mid < length - 1 && aFunction(anObject, self[mid + 1], aContext) == CPOrderedSame)
1023  mid++;
1024 
1025  return mid;
1026  }
1027  }
1028 
1029  var result = -first - 1;
1030 
1031  return result >= 0 ? result : CPNotFound;
1032 }
1033 
1034 @end
1035 
1036 var _sortRange = function(location, anObject)
1037 {
1038  if (CPLocationInRange(location, anObject._range))
1039  return CPOrderedSame;
1040  else if (CPMaxRange(anObject._range) <= location)
1041  return CPOrderedDescending;
1042  else
1043  return CPOrderedAscending;
1044 }
1045 
1046 var _objectWithLocationInRange = function(aList, aLocation)
1047 {
1048  var index = [aList _indexOfObject:aLocation sortedByFunction:_sortRange context:nil];
1049 
1050  if (index != CPNotFound)
1051  return aList[index];
1052 
1053  return nil;
1054 }
1055 
1056 var _objectsInRange = function(aList, aRange)
1057 {
1058  var firstIndex = [aList _indexOfObject:aRange.location sortedByFunction:_sortRange context:nil],
1059  lastIndex = [aList _indexOfObject:CPMaxRange(aRange) sortedByFunction:_sortRange context:nil];
1060 
1061  if (firstIndex === CPNotFound)
1062  firstIndex = 0;
1063 
1064  if (lastIndex === CPNotFound)
1065  lastIndex = aList.length - 1;
1066 
1067  return aList.slice(firstIndex, lastIndex + 1);
1068 }
1069 
1070 @implementation _CPLineFragment : CPObject
1071 {
1072  CPArray _glyphsFrames;
1073  CPArray _glyphsOffsets;
1074 
1075  BOOL _isInvalid;
1076  BOOL _isLast;
1077  CGRect _fragmentRect;
1078  CGRect _usedRect;
1079  CGPoint _location;
1080  CPRange _range;
1081  CPTextContainer _textContainer;
1082  CPMutableArray _runs;
1083 }
1084 
1085 #pragma mark -
1086 #pragma mark Init methods
1087 
1088 - (id)createDOMElementWithText:(CPString)aString andFont:(CPFont)aFont andColor:(CPColor)aColor
1089 {
1090 #if PLATFORM(DOM)
1091  var style,
1092  span = document.createElement("span");
1093 
1094  span.oncontextmenu = span.onmousedown = span.onselectstart = _oncontextmenuhandler;
1095  // span.contentEditable = true; // this unfortunately does not work to make native pasting work on safari
1096 
1097  style = span.style;
1098  style.position = "absolute";
1099  style.visibility = "visible";
1100  style.padding = "0px";
1101  style.margin = "0px";
1102  style.whiteSpace = "pre";
1103  style.backgroundColor = "transparent";
1104  style.font = [aFont cssString];
1105 
1106  if (aColor)
1107  style.color = [aColor cssString];
1108 
1110  span.innerText = aString;
1112  span.textContent = aString;
1113 
1114  //<!> FIXME aString.replace(/&/g,'&amp;')
1115  return span;
1116 #else
1117  return nil;
1118 #endif
1119 }
1120 
1121 - (id)initWithRange:(CPRange)aRange textContainer:(CPTextContainer)aContainer textStorage:(CPTextStorage)textStorage
1122 {
1123  if (self = [super init])
1124  {
1125  var effectiveRange = CPMakeRange(0,0),
1126  location;
1127 
1128  _fragmentRect = CGRectMakeZero();
1129  _usedRect = CGRectMakeZero();
1130  _location = CGPointMakeZero();
1131  _range = CPMakeRangeCopy(aRange);
1132  _textContainer = aContainer;
1133  _isInvalid = NO;
1134  _runs = [[CPMutableArray alloc] init];
1135 
1136  for (location = aRange.location; location < CPMaxRange(aRange); location = CPMaxRange(effectiveRange))
1137  {
1138  var attributes = [textStorage attributesAtIndex:location effectiveRange:effectiveRange];
1139 
1140  effectiveRange = attributes ? CPIntersectionRange(aRange, effectiveRange) : aRange;
1141 
1142  var string = [textStorage._string substringWithRange:effectiveRange],
1143  font = [textStorage font] || [CPFont systemFontOfSize:12.0];
1144 
1145  if ([attributes containsKey:CPFontAttributeName])
1146  font = [attributes objectForKey:CPFontAttributeName];
1147 
1148  var color = [attributes objectForKey:CPForegroundColorAttributeName],
1149  elem = [self createDOMElementWithText:string andFont:font andColor:color],
1150  run = {_range:CPMakeRangeCopy(effectiveRange), color:color, font:font, elem:nil, string:string};
1151 
1152  _runs.push(run);
1153 
1154  if (!CPMaxRange(effectiveRange))
1155  break;
1156  }
1157  }
1158 
1159  return self;
1160 }
1161 
1162 - (void)setAdvancements:(CPArray)someAdvancements
1163 {
1164  var count = someAdvancements.length,
1165  origin = CGPointMake(_fragmentRect.origin.x + _location.x, _fragmentRect.origin.y),
1166  height = _usedRect.size.height;
1167 
1168  _glyphsFrames = new Array(count);
1169  _glyphsOffsets = new Array(count);
1170 
1171  for (var i = 0; i < count; i++)
1172  {
1173  _glyphsFrames[i] = CGRectMake(origin.x, origin.y, someAdvancements[i].width, height);
1174  _glyphsFrames[i]._descent = someAdvancements[i].descent;
1175  _glyphsOffsets[i] = height - someAdvancements[i].height;
1176  origin.x += someAdvancements[i].width;
1177  }
1178 }
1179 
1180 - (void)_adjustForHeight:(double)height
1181 {
1182  var count = _glyphsFrames.length;
1183 
1184  for (var i = 0; i < count; i++)
1185  _glyphsFrames[i].origin.y += (height - _fragmentRect.size.height);
1186 
1187  _fragmentRect.size.height = height;
1188 }
1189 
1191 {
1192  return [super description] +
1193  "\n\t_fragmentRect="+CPStringFromRect(_fragmentRect) +
1194  "\n\t_usedRect="+CPStringFromRect(_usedRect) +
1195  "\n\t_location="+CPStringFromPoint(_location) +
1196  "\n\t_range="+CPStringFromRange(_range);
1197 }
1198 
1199 - (void)drawUnderlineForGlyphRange:(CPRange)glyphRange
1200  underlineType:(int)underlineVal
1201  baselineOffset:(float)baselineOffset
1202  containerOrigin:(CGPoint)containerOrigin
1203 {
1204 // <!> FIXME
1205 }
1206 
1207 - (void)invalidate
1208 {
1209  _isInvalid = YES;
1210 }
1211 
1212 - (void)_deinvalidate
1213 {
1214  _isInvalid = NO;
1215 }
1216 
1217 - (void)_removeFromDOM
1218 {
1219  var l = _runs.length;
1220 
1221  for (var i = 0; i < l; i++)
1222  {
1223  if (_runs[i].elem && _runs[i].DOMactive)
1224  _textContainer._textView._DOMElement.removeChild(_runs[i].elem);
1225 
1226  _runs[i].elem = nil;
1227  _runs[i].DOMactive = NO;
1228  }
1229 }
1230 
1231 - (void)drawInContext:(CGContext)context atPoint:(CGPoint)aPoint forRange:(CPRange)aRange
1232 {
1233  var runs = _objectsInRange(_runs, aRange),
1234  c = runs.length,
1235  orig = CGPointMake(_fragmentRect.origin.x, _fragmentRect.origin.y);
1236 
1237  for (var i = 0; i < c; i++)
1238  {
1239  var run = runs[i];
1240 
1241  if (!run.elem && CPRectIntersectsRect([_textContainer._textView exposedRect], _fragmentRect))
1242  {
1243  run.elem=[self createDOMElementWithText:run.string andFont:run.font andColor:run.color];
1244  }
1245 
1246  if (run.DOMactive && !run.DOMpatched)
1247  continue;
1248 
1249  if (!_glyphsFrames)
1250  continue;
1251 
1252  var loc = run._range.location - _runs[0]._range.location;
1253  orig.x = _glyphsFrames[loc].origin.x + aPoint.x;
1254  orig.y = _glyphsFrames[loc].origin.y + aPoint.y + _glyphsOffsets[loc];
1255 
1256  if(run.elem)
1257  {
1258  run.elem.style.left = (orig.x) + "px";
1259  run.elem.style.top = (orig.y) + "px";
1260 
1261  if (!run.DOMactive)
1262  _textContainer._textView._DOMElement.appendChild(run.elem);
1263 
1264  run.DOMactive = YES;
1265  }
1266 
1267  run.DOMpatched = NO;
1268 
1269  }
1270 }
1271 
1272 - (void)backgroundColorForGlyphAtIndex:(unsigned)index
1273 {
1274  var run = _objectWithLocationInRange(_runs, index);
1275 
1276  if (run)
1277  return run.backgroundColor;
1278 
1279  return [CPColor clearColor];
1280 }
1281 
1282 - (BOOL)isVisuallyIdenticalToFragment:(_CPLineFragment)newLineFragment
1283 {
1284  var newFragmentRuns= newLineFragment._runs,
1285  oldFragmentRuns= _runs;
1286 
1287  if (!oldFragmentRuns || !newFragmentRuns || oldFragmentRuns.length !== newFragmentRuns.length)
1288  return NO;
1289 
1290  var l = oldFragmentRuns.length;
1291 
1292  for (var i = 0; i < l; i++)
1293  {
1294  if (newFragmentRuns[i].string !== oldFragmentRuns[i].string)
1295  return NO;
1296 
1297  if (!_RectEqualToRectHorizontally(newLineFragment._fragmentRect, _fragmentRect))
1298  return NO;
1299 
1300  if (newFragmentRuns[i].color !== oldFragmentRuns[i].color || newFragmentRuns[i].font !== oldFragmentRuns[i].font)
1301  return NO;
1302 
1303  }
1304 
1305  return YES;
1306 }
1307 
1308 - (void)_relocateVerticallyByY:(double)verticalOffset rangeOffset:(unsigned)rangeOffset
1309 {
1310  var l = _runs.length;
1311 
1312  _range.location += rangeOffset;
1313 
1314  for (var i = 0; i < l; i++)
1315  {
1316  _runs[i]._range.location += rangeOffset;
1317 
1318  if (verticalOffset && _runs[i].elem)
1319  {
1320  _runs[i].elem.top = (_runs[i].elem.top + verticalOffset) + 'px';
1321  _runs[i].DOMpatched = YES;
1322  }
1323  }
1324 
1325  if (!verticalOffset)
1326  return NO;
1327 
1328  _fragmentRect.origin.y += verticalOffset;
1329  _usedRect.origin.y += verticalOffset;
1330 
1331  var l = _glyphsFrames.length;
1332 
1333  for (var i = 0; i < l ; i++)
1334  {
1335  _glyphsFrames[i].origin.y += verticalOffset;
1336  }
1337 }
1338 
1339 @end
1340 
1341 @implementation _CPTemporaryAttributes : CPObject
1342 {
1343  CPDictionary _attributes;
1344  CPRange _range;
1345 }
1346 
1347 - (id)initWithRange:(CPRange)aRange attributes:(CPDictionary)attributes
1348 {
1349  if (self = [super init])
1350  {
1351  _attributes = attributes;
1352  _range = CPMakeRangeCopy(aRange);
1353  }
1354 
1355  return self;
1356 }
1357 
1359 {
1360  return [super description] +
1361  "\n\t_range="+CPStringFromRange(_range) +
1362  "\n\t_attributes="+[_attributes description];
1363 }
1364 
1365 @end
1366 
1367 @implementation CPLayoutManager (CPSynthesizedAccessors)
1368 
1372 - (void)setLineFragmentFactory:(Class)aValue
1373 {
1374  _lineFragmentFactory = aValue;
1375 }
1376 
1380 - (CPMutableArray)textContainers
1381 {
1382  return _textContainers;
1383 }
1384 
1388 - (CPTextStorage)textStorage
1389 {
1390  return _textStorage;
1391 }
1392 
1396 - (void)setTextStorage:(CPTextStorage)aValue
1397 {
1398  _textStorage = aValue;
1399 }
1400 
1404 - (CPTypesetter)typesetter
1405 {
1406  return _typesetter;
1407 }
1408 
1412 - (void)setTypesetter:(CPTypesetter)aValue
1413 {
1414  _typesetter = aValue;
1415 }
1416 
1417 @end
Definition: CPFont.h:2
CPOrderedAscending
Definition: CPObjJRuntime.j:48
function CPUnionRange(lhsRange, rhsRange)
Definition: CPRange.j:106
id init()
Definition: CALayer.j:126
CPOrderedSame
Definition: CPObjJRuntime.j:54
CPFont systemFontOfSize:(CGSize aSize)
Definition: CPFont.j:282
CPTextContainer textContainerForGlyphAtIndex:effectiveRange:withoutAdditionalLayout:(unsigned index, [effectiveRange] CPRangePointer effectiveGlyphRange, [withoutAdditionalLayout] BOOL flag)
CPColor clearColor()
Definition: CPColor.j:451
CPString cssString()
Definition: CPColor.j:756
CPStringFromRect
Definition: CPGeometry.j:339
CPResponder firstResponder()
Definition: CPWindow.j:1657
CPJavaScriptInnerTextFeature
CPDictionary attributesAtIndex:effectiveRange:(CPUInteger anIndex, [effectiveRange] CPRangePointer aRange)
int width
void sizeToFit()
Definition: CPTextView.j:1900
A mutable key-value pair collection.
Definition: CPDictionary.h:2
function CPStringFromRange(aRange)
Definition: CPRange.j:148
CPTextView firstTextView()
function CPMaxRange(aRange)
Definition: CPRange.j:70
An immutable string (collection of characters).
Definition: CPString.h:2
unsigned glyphIndexForPoint:inTextContainer:fractionOfDistanceThroughGlyph:(CGPoint point, [inTextContainer] CPTextContainer container, [fractionOfDistanceThroughGlyph] FloatArray partialFraction)
CPJavaScriptTextContentFeature
if(CPFeatureIsCompatible(CPHTMLCanvasFeature))
function CPFeatureIsCompatible(aFeature)
CPOrderedDescending
Definition: CPObjJRuntime.j:60
void invalidateDisplayForGlyphRange:(CPRange range)
var CPLayoutManagerTextStorageKey
function CPIntersectionRange(lhsRange, rhsRange)
Definition: CPRange.j:120
CPStringFromPoint
Definition: CPGeometry.j:323
function CPMakeRangeCopy(aRange)
Definition: CPRange.j:48
Defines methods for use when archiving & restoring (enc/decoding).
Definition: CPCoder.h:2
CPFontAttributeName
Definition: CPText.j:99
CPNotFound
Definition: CPObjJRuntime.j:62
CPString cssString()
Definition: CPFont.j:383
id sharedSystemTypesetter()
Definition: CPTypesetter.j:57
id init()
Definition: CPObject.j:145
function CPLocationInRange(aLocation, aRange)
Definition: CPRange.j:93
void invalidateLayoutForCharacterRange:isSoft:actualCharacterRange:(CPRange aRange, [isSoft] BOOL flag, [actualCharacterRange] CPRangePointer actualCharRange)
CompletionHandlerAgent prototype invalidate
void setLayoutManager:(CPLayoutManager aValue)
CPRange function CPMakeRange(location, length)
Definition: CPRange.j:37
CPRectIntersectsRect
Definition: CPGeometry.j:287
id alloc()
Definition: CPObject.j:130
FrameUpdater prototype description