| OLD | NEW |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #import "ios/chrome/browser/ui/util/manual_text_framer.h" | 5 #import "ios/chrome/browser/ui/util/manual_text_framer.h" |
| 6 | 6 |
| 7 #import <UIKit/UIKit.h> | 7 #import <UIKit/UIKit.h> |
| 8 | 8 |
| 9 #include "base/i18n/rtl.h" | 9 #include "base/i18n/rtl.h" |
| 10 #include "base/logging.h" | 10 #include "base/logging.h" |
| 11 #include "base/mac/foundation_util.h" | 11 #include "base/mac/foundation_util.h" |
| 12 #include "base/mac/scoped_nsobject.h" | |
| 13 #import "ios/chrome/browser/ui/util/core_text_util.h" | 12 #import "ios/chrome/browser/ui/util/core_text_util.h" |
| 14 #import "ios/chrome/browser/ui/util/text_frame.h" | 13 #import "ios/chrome/browser/ui/util/text_frame.h" |
| 15 #import "ios/chrome/browser/ui/util/unicode_util.h" | 14 #import "ios/chrome/browser/ui/util/unicode_util.h" |
| 16 | 15 |
| 16 #if !defined(__has_feature) || !__has_feature(objc_arc) |
| 17 #error "This file requires ARC support." |
| 18 #endif |
| 19 |
| 17 // NOTE: When RTL text is laid out into glyph runs, the glyphs appear in the | 20 // NOTE: When RTL text is laid out into glyph runs, the glyphs appear in the |
| 18 // visual order in which they appear on screen. In other words, the glyphs are | 21 // visual order in which they appear on screen. In other words, the glyphs are |
| 19 // arranged in the reverse order of their corresponding characters in the | 22 // arranged in the reverse order of their corresponding characters in the |
| 20 // original string. | 23 // original string. |
| 21 | 24 |
| 22 namespace { | 25 namespace { |
| 23 // Aligns |value| to the nearest pixel value, rounding by the function indicated | 26 // Aligns |value| to the nearest pixel value, rounding by the function indicated |
| 24 // by |function|. AlignmentFunction::CEIL should be used to align size values, | 27 // by |function|. AlignmentFunction::CEIL should be used to align size values, |
| 25 // while AlignmentFunction::FLOOR should be used to align location values. | 28 // while AlignmentFunction::FLOOR should be used to align location values. |
| 26 enum class AlignmentFunction : short { CEIL = 0, FLOOR }; | 29 enum class AlignmentFunction : short { CEIL = 0, FLOOR }; |
| (...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 63 } | 66 } |
| 64 return paragraph_strings; | 67 return paragraph_strings; |
| 65 } | 68 } |
| 66 } // namespace | 69 } // namespace |
| 67 | 70 |
| 68 #pragma mark - ManualTextFrame | 71 #pragma mark - ManualTextFrame |
| 69 | 72 |
| 70 // A TextFrame implementation that is manually created by ManualTextFramer. | 73 // A TextFrame implementation that is manually created by ManualTextFramer. |
| 71 @interface ManualTextFrame : NSObject<TextFrame> { | 74 @interface ManualTextFrame : NSObject<TextFrame> { |
| 72 // Backing objects for properties of the same name. | 75 // Backing objects for properties of the same name. |
| 73 base::scoped_nsobject<NSAttributedString> _string; | 76 NSAttributedString* _string; |
| 74 base::scoped_nsobject<NSMutableArray> _lines; | 77 NSMutableArray* _lines; |
| 75 } | 78 } |
| 76 | 79 |
| 77 // Designated initializer. | 80 // Designated initializer. |
| 78 - (instancetype)initWithString:(NSAttributedString*)string | 81 - (instancetype)initWithString:(NSAttributedString*)string |
| 79 inBounds:(CGRect)bounds NS_DESIGNATED_INITIALIZER; | 82 inBounds:(CGRect)bounds NS_DESIGNATED_INITIALIZER; |
| 80 - (instancetype)init NS_UNAVAILABLE; | 83 - (instancetype)init NS_UNAVAILABLE; |
| 81 | 84 |
| 82 // Creates a FramedLine out of |line|, |stringRange|, and |origin|, then adds it | 85 // Creates a FramedLine out of |line|, |stringRange|, and |origin|, then adds it |
| 83 // to |lines|. | 86 // to |lines|. |
| 84 - (void)addFramedLineWithLine:(CTLineRef)line | 87 - (void)addFramedLineWithLine:(CTLineRef)line |
| 85 stringRange:(NSRange)stringRange | 88 stringRange:(NSRange)stringRange |
| 86 origin:(CGPoint)origin; | 89 origin:(CGPoint)origin; |
| 87 | 90 |
| 88 // Redefine property as readwrite. | 91 // Redefine property as readwrite. |
| 89 @property(nonatomic, readwrite) NSRange framedRange; | 92 @property(nonatomic, readwrite) NSRange framedRange; |
| 90 | 93 |
| 91 @end | 94 @end |
| 92 | 95 |
| 93 @implementation ManualTextFrame | 96 @implementation ManualTextFrame |
| 94 | 97 |
| 95 @synthesize framedRange = _framedRange; | 98 @synthesize framedRange = _framedRange; |
| 96 @synthesize bounds = _bounds; | 99 @synthesize bounds = _bounds; |
| 97 | 100 |
| 98 - (instancetype)initWithString:(NSAttributedString*)string | 101 - (instancetype)initWithString:(NSAttributedString*)string |
| 99 inBounds:(CGRect)bounds { | 102 inBounds:(CGRect)bounds { |
| 100 if ((self = [super init])) { | 103 if ((self = [super init])) { |
| 101 DCHECK(string.string.length); | 104 DCHECK(string.string.length); |
| 102 _string.reset([string retain]); | 105 _string = string; |
| 103 _bounds = bounds; | 106 _bounds = bounds; |
| 104 _lines.reset([[NSMutableArray alloc] init]); | 107 _lines = [[NSMutableArray alloc] init]; |
| 105 } | 108 } |
| 106 return self; | 109 return self; |
| 107 } | 110 } |
| 108 | 111 |
| 109 #pragma mark Accessors | 112 #pragma mark Accessors |
| 110 | 113 |
| 111 - (NSAttributedString*)string { | 114 - (NSAttributedString*)string { |
| 112 return _string.get(); | 115 return _string; |
| 113 } | 116 } |
| 114 | 117 |
| 115 - (NSArray*)lines { | 118 - (NSArray*)lines { |
| 116 return _lines.get(); | 119 return _lines; |
| 117 } | 120 } |
| 118 | 121 |
| 119 #pragma mark Private | 122 #pragma mark Private |
| 120 | 123 |
| 121 - (void)addFramedLineWithLine:(CTLineRef)line | 124 - (void)addFramedLineWithLine:(CTLineRef)line |
| 122 stringRange:(NSRange)stringRange | 125 stringRange:(NSRange)stringRange |
| 123 origin:(CGPoint)origin { | 126 origin:(CGPoint)origin { |
| 124 base::scoped_nsobject<FramedLine> framedLine([[FramedLine alloc] | 127 FramedLine* framedLine = [[FramedLine alloc] initWithLine:line |
| 125 initWithLine:line | 128 stringRange:stringRange |
| 126 stringRange:stringRange | 129 origin:origin]; |
| 127 origin:origin]); | |
| 128 [_lines addObject:framedLine]; | 130 [_lines addObject:framedLine]; |
| 129 } | 131 } |
| 130 | 132 |
| 131 @end | 133 @end |
| 132 | 134 |
| 133 #pragma mark - ManualTextFramer Private Interface | 135 #pragma mark - ManualTextFramer Private Interface |
| 134 | 136 |
| 135 @interface ManualTextFramer () { | 137 @interface ManualTextFramer () |
| 136 // Backing objects for properties of the same name. | |
| 137 base::scoped_nsobject<NSAttributedString> _string; | |
| 138 base::scoped_nsobject<ManualTextFrame> _manualTextFrame; | |
| 139 } | |
| 140 | 138 |
| 141 // The string passed upon initialization. | 139 // The string passed upon initialization. |
| 142 @property(nonatomic, readonly) NSAttributedString* string; | 140 @property(strong, nonatomic, readonly) NSAttributedString* string; |
| 143 | 141 |
| 144 // The bounds passed upon initialization. | 142 // The bounds passed upon initialization. |
| 145 @property(nonatomic, readonly) CGRect bounds; | 143 @property(nonatomic, readonly) CGRect bounds; |
| 146 | 144 |
| 147 // The width of the bounds passed upon initialization. | 145 // The width of the bounds passed upon initialization. |
| 148 @property(nonatomic, readonly) CGFloat boundingWidth; | 146 @property(nonatomic, readonly) CGFloat boundingWidth; |
| 149 | 147 |
| 150 // The remaining height into which text can be framed. | 148 // The remaining height into which text can be framed. |
| 151 @property(nonatomic, assign) CGFloat remainingHeight; | 149 @property(nonatomic, assign) CGFloat remainingHeight; |
| 152 | 150 |
| 153 // The text frame constructed by |-frameText|. | 151 // The text frame constructed by |-frameText|. |
| 154 @property(nonatomic, readonly) ManualTextFrame* manualTextFrame; | 152 @property(strong, nonatomic, readonly) ManualTextFrame* manualTextFrame; |
| 155 | 153 |
| 156 // Creates a ManualTextFrame and assigns it to |_manualTextFrame|. Returns YES | 154 // Creates a ManualTextFrame and assigns it to |_manualTextFrame|. Returns YES |
| 157 // if a new text frame was successfully created. | 155 // if a new text frame was successfully created. |
| 158 - (BOOL)setupManualTextFrame; | 156 - (BOOL)setupManualTextFrame; |
| 159 | 157 |
| 160 @end | 158 @end |
| 161 | 159 |
| 162 #pragma mark - ParagraphFramer | 160 #pragma mark - ParagraphFramer |
| 163 | 161 |
| 164 // ManualTextFramer subclass that frames a single paragraph. A paragraph is | 162 // ManualTextFramer subclass that frames a single paragraph. A paragraph is |
| 165 // defined as an NSAttributedString which contains either zero newlines or one | 163 // defined as an NSAttributedString which contains either zero newlines or one |
| 166 // newline as its last character. | 164 // newline as its last character. |
| 167 @interface ParagraphFramer : ManualTextFramer { | 165 @interface ParagraphFramer : ManualTextFramer { |
| 168 // Backing objects for properties of the same name. | 166 // Backing objects for properties of the same name. |
| 169 base::ScopedCFTypeRef<CTLineRef> _line; | 167 base::ScopedCFTypeRef<CTLineRef> _line; |
| 170 base::scoped_nsobject<NSCharacterSet> _lineEndSet; | |
| 171 } | 168 } |
| 172 | 169 |
| 173 // The CTLine created from |string|. | 170 // The CTLine created from |string|. |
| 174 @property(nonatomic, readonly) CTLineRef line; | 171 @property(nonatomic, readonly) CTLineRef line; |
| 175 | 172 |
| 176 // The effective text alignment for |line|. | 173 // The effective text alignment for |line|. |
| 177 @property(nonatomic, readonly) NSTextAlignment effectiveAlignment; | 174 @property(nonatomic, readonly) NSTextAlignment effectiveAlignment; |
| 178 | 175 |
| 179 // Character set containing characters that are appropriate for line endings. | 176 // Character set containing characters that are appropriate for line endings. |
| 180 // These characters include whitespaces and newlines (denoting a word boundary), | 177 // These characters include whitespaces and newlines (denoting a word boundary), |
| 181 // in addition to line-ending characters like hyphens, em dashes, and en dashes. | 178 // in addition to line-ending characters like hyphens, em dashes, and en dashes. |
| 182 @property(nonatomic, readonly) NSCharacterSet* lineEndSet; | 179 @property(strong, nonatomic, readonly) NSCharacterSet* lineEndSet; |
| 183 | 180 |
| 184 // The index of the current run that is being framed. Setting |runIdx| also | 181 // The index of the current run that is being framed. Setting |runIdx| also |
| 185 // updates |currentRun| and |currentGlyphCount|. | 182 // updates |currentRun| and |currentGlyphCount|. |
| 186 @property(nonatomic, assign) CFIndex runIdx; | 183 @property(nonatomic, assign) CFIndex runIdx; |
| 187 | 184 |
| 188 // The CTRun corresponding with |runIdx| in |line|. | 185 // The CTRun corresponding with |runIdx| in |line|. |
| 189 @property(nonatomic, readonly) CTRunRef currentRun; | 186 @property(nonatomic, readonly) CTRunRef currentRun; |
| 190 | 187 |
| 191 // The glyph count in |currentRun|. | 188 // The glyph count in |currentRun|. |
| 192 @property(nonatomic, readonly) CFIndex currentGlyphCount; | 189 @property(nonatomic, readonly) CFIndex currentGlyphCount; |
| (...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 264 | 261 |
| 265 @synthesize effectiveAlignment = _effectiveTextAlignment; | 262 @synthesize effectiveAlignment = _effectiveTextAlignment; |
| 266 @synthesize runIdx = _runIdx; | 263 @synthesize runIdx = _runIdx; |
| 267 @synthesize currentRun = _currentRun; | 264 @synthesize currentRun = _currentRun; |
| 268 @synthesize currentGlyphCount = _currentGlyphCount; | 265 @synthesize currentGlyphCount = _currentGlyphCount; |
| 269 @synthesize framedGlyphCount = _framedGlyphCount; | 266 @synthesize framedGlyphCount = _framedGlyphCount; |
| 270 @synthesize currentLineRange = _currentLineRange; | 267 @synthesize currentLineRange = _currentLineRange; |
| 271 @synthesize currentLineWidth = _currentLineWidth; | 268 @synthesize currentLineWidth = _currentLineWidth; |
| 272 @synthesize currentWhitespaceWidth = _currentWhitespaceWidth; | 269 @synthesize currentWhitespaceWidth = _currentWhitespaceWidth; |
| 273 @synthesize isRTL = _isRTL; | 270 @synthesize isRTL = _isRTL; |
| 271 @synthesize lineEndSet = _lineEndSet; |
| 274 | 272 |
| 275 - (instancetype)initWithString:(NSAttributedString*)string | 273 - (instancetype)initWithString:(NSAttributedString*)string |
| 276 inBounds:(CGRect)bounds { | 274 inBounds:(CGRect)bounds { |
| 277 if ((self = [super initWithString:string inBounds:bounds])) { | 275 if ((self = [super initWithString:string inBounds:bounds])) { |
| 278 NSRange newlineRange = [string.string | 276 NSRange newlineRange = [string.string |
| 279 rangeOfCharacterFromSet:[NSCharacterSet newlineCharacterSet]]; | 277 rangeOfCharacterFromSet:[NSCharacterSet newlineCharacterSet]]; |
| 280 DCHECK(newlineRange.location == NSNotFound || | 278 DCHECK(newlineRange.location == NSNotFound || |
| 281 newlineRange.location == string.string.length - 1); | 279 newlineRange.location == string.string.length - 1); |
| 282 CTLineRef line = | 280 CTLineRef line = |
| 283 CTLineCreateWithAttributedString(base::mac::NSToCFCast(string)); | 281 CTLineCreateWithAttributedString(base::mac::NSToCFCast(string)); |
| (...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 336 | 334 |
| 337 - (CTLineRef)line { | 335 - (CTLineRef)line { |
| 338 return _line.get(); | 336 return _line.get(); |
| 339 } | 337 } |
| 340 | 338 |
| 341 - (NSCharacterSet*)lineEndSet { | 339 - (NSCharacterSet*)lineEndSet { |
| 342 if (!_lineEndSet) { | 340 if (!_lineEndSet) { |
| 343 NSMutableCharacterSet* lineEndSet = | 341 NSMutableCharacterSet* lineEndSet = |
| 344 [NSMutableCharacterSet whitespaceAndNewlineCharacterSet]; | 342 [NSMutableCharacterSet whitespaceAndNewlineCharacterSet]; |
| 345 [lineEndSet addCharactersInString:@"-\u2013\u2014"]; | 343 [lineEndSet addCharactersInString:@"-\u2013\u2014"]; |
| 346 _lineEndSet.reset([lineEndSet retain]); | 344 _lineEndSet = lineEndSet; |
| 347 } | 345 } |
| 348 return _lineEndSet; | 346 return _lineEndSet; |
| 349 } | 347 } |
| 350 | 348 |
| 351 - (void)setRunIdx:(CFIndex)runIdx { | 349 - (void)setRunIdx:(CFIndex)runIdx { |
| 352 _runIdx = runIdx; | 350 _runIdx = runIdx; |
| 353 self.framedGlyphCount = 0; | 351 self.framedGlyphCount = 0; |
| 354 if ([self runIdxIsValid:runIdx]) { | 352 if ([self runIdxIsValid:runIdx]) { |
| 355 NSArray* runs = base::mac::CFToNSCast(CTLineGetGlyphRuns(self.line)); | 353 NSArray* runs = base::mac::CFToNSCast(CTLineGetGlyphRuns(self.line)); |
| 356 _currentRun = static_cast<CTRunRef>(runs[_runIdx]); | 354 _currentRun = (__bridge CTRunRef)(runs[_runIdx]); |
| 357 _currentGlyphCount = CTRunGetGlyphCount(self.currentRun); | 355 _currentGlyphCount = CTRunGetGlyphCount(self.currentRun); |
| 358 } else { | 356 } else { |
| 359 _currentRun = nullptr; | 357 _currentRun = nullptr; |
| 360 _currentGlyphCount = 0; | 358 _currentGlyphCount = 0; |
| 361 } | 359 } |
| 362 } | 360 } |
| 363 | 361 |
| 364 - (CFIndex)incrementAmount { | 362 - (CFIndex)incrementAmount { |
| 365 return self.isRTL ? -1 : 1; | 363 return self.isRTL ? -1 : 1; |
| 366 } | 364 } |
| (...skipping 188 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 555 | 553 |
| 556 @end | 554 @end |
| 557 | 555 |
| 558 #pragma mark - ManualTextFramer | 556 #pragma mark - ManualTextFramer |
| 559 | 557 |
| 560 @implementation ManualTextFramer | 558 @implementation ManualTextFramer |
| 561 | 559 |
| 562 @synthesize bounds = _bounds; | 560 @synthesize bounds = _bounds; |
| 563 @synthesize boundingWidth = _boundingWidth; | 561 @synthesize boundingWidth = _boundingWidth; |
| 564 @synthesize remainingHeight = _remainingHeight; | 562 @synthesize remainingHeight = _remainingHeight; |
| 563 @synthesize string = _string; |
| 564 @synthesize manualTextFrame = _manualTextFrame; |
| 565 | 565 |
| 566 - (instancetype)initWithString:(NSAttributedString*)string | 566 - (instancetype)initWithString:(NSAttributedString*)string |
| 567 inBounds:(CGRect)bounds { | 567 inBounds:(CGRect)bounds { |
| 568 if ((self = [super init])) { | 568 if ((self = [super init])) { |
| 569 DCHECK(string.string.length); | 569 DCHECK(string.string.length); |
| 570 _string.reset([string retain]); | 570 _string = string; |
| 571 _bounds = bounds; | 571 _bounds = bounds; |
| 572 _boundingWidth = CGRectGetWidth(bounds); | 572 _boundingWidth = CGRectGetWidth(bounds); |
| 573 _remainingHeight = CGRectGetHeight(bounds); | 573 _remainingHeight = CGRectGetHeight(bounds); |
| 574 } | 574 } |
| 575 return self; | 575 return self; |
| 576 } | 576 } |
| 577 | 577 |
| 578 - (void)frameText { | 578 - (void)frameText { |
| 579 if (![self setupManualTextFrame]) | 579 if (![self setupManualTextFrame]) |
| 580 return; | 580 return; |
| 581 NSRange framedRange = NSMakeRange(0, 0); | 581 NSRange framedRange = NSMakeRange(0, 0); |
| 582 NSArray* paragraphs = GetParagraphStringsForString(self.string); | 582 NSArray* paragraphs = GetParagraphStringsForString(self.string); |
| 583 NSUInteger stringRangeOffset = 0; | 583 NSUInteger stringRangeOffset = 0; |
| 584 for (NSAttributedString* paragraph in paragraphs) { | 584 for (NSAttributedString* paragraph in paragraphs) { |
| 585 // Frame each paragraph using a ParagraphFramer, then update bookkeeping | 585 // Frame each paragraph using a ParagraphFramer, then update bookkeeping |
| 586 // variables for the top-level ManualTextFramer. | 586 // variables for the top-level ManualTextFramer. |
| 587 CGRect remainingBounds = | 587 CGRect remainingBounds = |
| 588 CGRectMake(0, 0, self.boundingWidth, self.remainingHeight); | 588 CGRectMake(0, 0, self.boundingWidth, self.remainingHeight); |
| 589 base::scoped_nsobject<ParagraphFramer> framer([[ParagraphFramer alloc] | 589 ParagraphFramer* framer = |
| 590 initWithString:paragraph | 590 [[ParagraphFramer alloc] initWithString:paragraph |
| 591 inBounds:remainingBounds]); | 591 inBounds:remainingBounds]; |
| 592 [framer frameText]; | 592 [framer frameText]; |
| 593 id<TextFrame> frame = [framer textFrame]; | 593 id<TextFrame> frame = [framer textFrame]; |
| 594 DCHECK(frame); | 594 DCHECK(frame); |
| 595 framedRange.length += frame.framedRange.length; | 595 framedRange.length += frame.framedRange.length; |
| 596 CGFloat paragraphHeight = 0.0; | 596 CGFloat paragraphHeight = 0.0; |
| 597 for (FramedLine* line in frame.lines) { | 597 for (FramedLine* line in frame.lines) { |
| 598 NSRange lineRange = line.stringRange; | 598 NSRange lineRange = line.stringRange; |
| 599 lineRange.location += stringRangeOffset; | 599 lineRange.location += stringRangeOffset; |
| 600 [self.manualTextFrame addFramedLineWithLine:line.line | 600 [self.manualTextFrame addFramedLineWithLine:line.line |
| 601 stringRange:lineRange | 601 stringRange:lineRange |
| 602 origin:line.origin]; | 602 origin:line.origin]; |
| 603 paragraphHeight += core_text_util::GetLineHeight(self.string, lineRange) + | 603 paragraphHeight += core_text_util::GetLineHeight(self.string, lineRange) + |
| 604 core_text_util::GetLineSpacing(self.string, lineRange); | 604 core_text_util::GetLineSpacing(self.string, lineRange); |
| 605 } | 605 } |
| 606 self.remainingHeight -= paragraphHeight; | 606 self.remainingHeight -= paragraphHeight; |
| 607 stringRangeOffset += paragraph.string.length; | 607 stringRangeOffset += paragraph.string.length; |
| 608 } | 608 } |
| 609 self.manualTextFrame.framedRange = framedRange; | 609 self.manualTextFrame.framedRange = framedRange; |
| 610 } | 610 } |
| 611 | 611 |
| 612 #pragma mark Accessors | 612 #pragma mark Accessors |
| 613 | 613 |
| 614 - (NSAttributedString*)string { | |
| 615 return _string.get(); | |
| 616 } | |
| 617 | |
| 618 - (ManualTextFrame*)manualTextFrame { | |
| 619 return _manualTextFrame.get(); | |
| 620 } | |
| 621 | |
| 622 - (id<TextFrame>)textFrame { | 614 - (id<TextFrame>)textFrame { |
| 623 return _manualTextFrame.get(); | 615 return _manualTextFrame; |
| 624 } | 616 } |
| 625 | 617 |
| 626 #pragma mark Private | 618 #pragma mark Private |
| 627 | 619 |
| 628 - (BOOL)setupManualTextFrame { | 620 - (BOOL)setupManualTextFrame { |
| 629 if (_manualTextFrame) | 621 if (_manualTextFrame) |
| 630 return NO; | 622 return NO; |
| 631 _manualTextFrame.reset([[ManualTextFrame alloc] initWithString:self.string | 623 _manualTextFrame = |
| 632 inBounds:self.bounds]); | 624 [[ManualTextFrame alloc] initWithString:self.string inBounds:self.bounds]; |
| 633 return YES; | 625 return YES; |
| 634 } | 626 } |
| 635 | 627 |
| 636 @end | 628 @end |
| OLD | NEW |