<div dir="ltr"><div><div><div><div>I did not find regressions, but this part lacks automated tests (really).<br></div><br>Having a space crossing rightMargin on last line is not handled correctly despite the machinery found in crossedX...<br>
But is wasn&#39;t before those changes...<br></div><div>Though, a MVC Paragraph makes a better job in this case...<br><br></div>I can&#39;t understand why theFirstCharCrossedX is required, nor this section of endOfRun:<br>
    characterIndex ~~ nil<br>        ifTrue:    [&quot;If scanning for an index and we&#39;ve stopped on that index,<br>                then we back destX off by the width of the character stopped on<br>                (it will be pointing at the right side of the character) and return&quot;<br>
                runStopIndex = characterIndex<br>                    ifTrue:    [characterPoint := destX - lastCharacterWidth @ destY.<br>                            ^true].<br></div>They might be related, but not sure...<br>
<br></div>rightFlush still is messy like it was, but it might be at composition stage too.<br></div><div class="gmail_extra"><br><br><div class="gmail_quote">2013/10/1  <span dir="ltr">&lt;<a href="mailto:commits@source.squeak.org" target="_blank">commits@source.squeak.org</a>&gt;</span><br>
<blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">Nicolas Cellier uploaded a new version of Graphics to project The Trunk:<br>
<a href="http://source.squeak.org/trunk/Graphics-nice.242.mcz" target="_blank">http://source.squeak.org/trunk/Graphics-nice.242.mcz</a><br>
<br>
==================== Summary ====================<br>
<br>
Name: Graphics-nice.242<br>
Author: nice<br>
Time: 1 October 2013, 2:39:06.884 am<br>
UUID: ca886e46-dd2e-448d-a639-6cf5060948aa<br>
Ancestors: Graphics-tpr.241<br>
<br>
More simplification of CharacterBlockScanner<br>
1) Replace lastCharacterExtent with lastCharacterWidth : more simple.<br>
Thus we can remove lastCharacterExtentSetX:<br>
2) Don&#39;t record lastCharacterExtent + lastSpaceOrTabExtent it&#39;s too much.<br>
- A stopCondition will set the lastCharacterWidth.<br>
- For any other character, this can be retrieved on demand by retrieveLastCharacterWidth.<br>
So in the end, lastCharacterWidth is all we ever wanted.<br>
Thus we can remove lastSpaceOrTabExtentSetX:<br>
3) Move the hack for click after middle of last char with his colleagues in the stopCondition.<br>
This restores some homogeneity between MVC and Morphic code.<br>
Remove Yukky code.<br>
<br>
=============== Diff against Graphics-tpr.241 ===============<br>
<br>
Item was changed:<br>
  CharacterScanner subclass: #CharacterBlockScanner<br>
+       instanceVariableNames: &#39;characterPoint characterIndex nextLeftMargin specialWidth lastCharacterWidth&#39;<br>
-       instanceVariableNames: &#39;characterPoint characterIndex lastCharacterExtent lastSpaceOrTabExtent nextLeftMargin specialWidth&#39;<br>
        classVariableNames: &#39;&#39;<br>
        poolDictionaries: &#39;&#39;<br>
        category: &#39;Graphics-Text&#39;!<br>
<br>
  !CharacterBlockScanner commentStamp: &#39;&lt;historical&gt;&#39; prior: 0!<br>
  My instances are used to scan text to compute the CharacterBlock for a character specified by its index in the text or its proximity to the cursor location.!<br>
<br>
Item was changed:<br>
  ----- Method: CharacterBlockScanner&gt;&gt;buildCharacterBlockIn: (in category &#39;private&#39;) -----<br>
  buildCharacterBlockIn: para<br>
        &quot;This method is used by the MVC version only.&quot;<br>
<br>
        | lineIndex runLength lineStop stopCondition |<br>
        &quot;handle nullText&quot;<br>
        (para numberOfLines = 0 or: [text size = 0])<br>
                ifTrue: [^ CharacterBlock new stringIndex: 1  &quot;like being off end of string&quot;<br>
                                        text: para text<br>
                                        topLeft: (para leftMarginForDisplayForLine: 1 alignment: (alignment ifNil:[textStyle alignment]))<br>
                                                                @ para compositionRectangle top<br>
                                        extent: 0 @ textStyle lineGrid].<br>
        &quot;find the line&quot;<br>
        lineIndex := para lineIndexOfTop: characterPoint y.<br>
        destY := para topAtLineIndex: lineIndex.<br>
        line := para lines at: lineIndex.<br>
        lastIndex := line first.<br>
        rightMargin := para rightMarginForDisplay.<br>
        self setStopConditions.  &quot; also loads the font, alignment and all emphasis attributes &quot;<br>
<br>
        (lineIndex = para numberOfLines and:<br>
                [(destY + line lineHeight) &lt; characterPoint y])<br>
                        ifTrue: [&quot;if beyond lastLine, force search to last character&quot;<br>
                                        self characterPointSetX: rightMargin]<br>
                        ifFalse:        [characterPoint y &lt; (para compositionRectangle) top<br>
                                                ifTrue: [&quot;force search to first line&quot;<br>
                                                                characterPoint := (para compositionRectangle) topLeft].<br>
                                        characterPoint x &gt; rightMargin<br>
                                                ifTrue: [self characterPointSetX: rightMargin]].<br>
        destX := leftMargin := para leftMarginForDisplayForLine: lineIndex alignment: alignment.<br>
        nextLeftMargin:= para leftMarginForDisplayForLine: lineIndex+1 alignment: alignment.<br>
        runLength := text runLengthFor: line first.<br>
        lineStop := characterIndex      &quot;scanning for index&quot;<br>
                ifNil: [ line last ].                   &quot;scanning for point&quot;<br>
        runStopIndex := lastIndex + (runLength - 1) min: lineStop.<br>
+       lastCharacterWidth := 0.<br>
-       lastCharacterExtent := 0 @ line lineHeight.<br>
        spaceCount := 0.<br>
        self handleIndentation.<br>
<br>
        [stopCondition := self scanCharactersFrom: lastIndex to: runStopIndex<br>
                        in: text string rightX: characterPoint x<br>
                        stopConditions: stopConditions kern: kern.<br>
        &quot;see setStopConditions for stopping conditions for character block operations.&quot;<br>
-       self lastCharacterExtentSetX: (font widthOf: (text at: lastIndex)).<br>
        self perform: stopCondition] whileFalse.<br>
<br>
        ^characterIndex == nil<br>
                        ifTrue: [&quot;characterBlockAtPoint&quot;<br>
                                        ^ CharacterBlock new stringIndex: lastIndex text: text<br>
                                                topLeft: characterPoint + (font descentKern @ 0)<br>
+                                               extent: lastCharacterWidth @ line lineHeight]<br>
-                                               extent: lastCharacterExtent]<br>
                        ifFalse: [&quot;characterBlockForIndex&quot;<br>
                                        ^ CharacterBlock new stringIndex: lastIndex text: text<br>
                                                topLeft: characterPoint + ((font descentKern) - kern @ 0)<br>
+                                               extent: lastCharacterWidth @ line lineHeight]!<br>
-                                               extent: lastCharacterExtent]!<br>
<br>
Item was changed:<br>
  ----- Method: CharacterBlockScanner&gt;&gt;characterBlockAtPoint:index:in: (in category &#39;scanning&#39;) -----<br>
  characterBlockAtPoint: aPoint index: index in: textLine<br>
        &quot;This method is the Morphic characterBlock finder.  It combines<br>
        MVC&#39;s characterBlockAtPoint:, -ForIndex:, and buildCharacterBlockIn:&quot;<br>
        | runLength lineStop stopCondition |<br>
        line := textLine.<br>
        rightMargin := line rightMargin.<br>
        lastIndex := line first.<br>
        self setStopConditions.         &quot;also sets font&quot;<br>
        characterIndex := index.  &quot; == nil means scanning for point&quot;<br>
        characterPoint := aPoint.<br>
        (characterPoint isNil or: [characterPoint y &gt; line bottom])<br>
                ifTrue: [characterPoint := line bottomRight].<br>
        destX := leftMargin := line leftMarginForAlignment: alignment.<br>
        destY := line top.<br>
        (text isEmpty or: [(characterPoint y &lt; destY or: [characterPoint x &lt; destX])<br>
                                or: [characterIndex notNil and: [characterIndex &lt; line first]]])<br>
                ifTrue: [^ (CharacterBlock new stringIndex: line first text: text<br>
                                        topLeft: destX@destY extent: 0 @ textStyle lineGrid)<br>
                                        textLine: line].<br>
        runLength := text runLengthFor: line first.<br>
        lineStop := characterIndex      &quot;scanning for index&quot;<br>
                ifNil: [ line last ].                   &quot;scanning for point&quot;<br>
        runStopIndex := lastIndex + (runLength - 1) min: lineStop.<br>
+       lastCharacterWidth := 0.<br>
-       lastCharacterExtent := 0 @ line lineHeight.<br>
        spaceCount := 0.<br>
<br>
        [<br>
                stopCondition := self scanCharactersFrom: lastIndex to: runStopIndex<br>
                        in: text string rightX: characterPoint x<br>
                        stopConditions: stopConditions kern: kern.<br>
                &quot;see setStopConditions for stopping conditions for character block operations.&quot;<br>
-               self lastCharacterExtentSetX: (specialWidth ifNil: [font widthOf: (text at: lastIndex)]).<br>
                self perform: stopCondition<br>
        ] whileFalse.<br>
        characterIndex<br>
+               ifNil: [&quot;Result for characterBlockAtPoint: &quot;<br>
-               ifNil: [<br>
-                       &quot;Result for characterBlockAtPoint: &quot;<br>
-                       (stopCondition ~~ #cr and: [ lastIndex = line last<br>
-                               and: [ aPoint x &gt; ((characterPoint x) + (lastCharacterExtent x / 2)) ]])<br>
-                                       ifTrue: [ &quot;Correct for right half of last character in line&quot;<br>
-                                               ^ (CharacterBlock new stringIndex: lastIndex + 1<br>
-                                                               text: text<br>
-                                                               topLeft: characterPoint + (lastCharacterExtent x @ 0) + (font descentKern @ 0)<br>
-                                                               extent:  0 @ lastCharacterExtent y)<br>
-                                                       textLine: line ].<br>
                                ^ (CharacterBlock new<br>
                                        stringIndex: lastIndex<br>
                                        text: text topLeft: characterPoint + (font descentKern @ 0)<br>
+                                       extent: lastCharacterWidth @ line lineHeight - (font baseKern @ 0))<br>
-                                       extent: lastCharacterExtent - (font baseKern @ 0))<br>
                                                        textLine: line]<br>
                ifNotNil: [&quot;Result for characterBlockForIndex: &quot;<br>
                                ^ (CharacterBlock new<br>
                                        stringIndex: characterIndex<br>
                                        text: text topLeft: characterPoint + ((font descentKern) - kern @ 0)<br>
+                                       extent: lastCharacterWidth @ line lineHeight)<br>
-                                       extent: lastCharacterExtent)<br>
                                                        textLine: line]!<br>
<br>
Item was changed:<br>
  ----- Method: CharacterBlockScanner&gt;&gt;cr (in category &#39;stop conditions&#39;) -----<br>
  cr<br>
        &quot;Answer a CharacterBlock that specifies the current location of the mouse<br>
        relative to a carriage return stop condition that has just been<br>
        encountered. The ParagraphEditor convention is to denote selections by<br>
        CharacterBlocks, sometimes including the carriage return (cursor is at<br>
        the end) and sometimes not (cursor is in the middle of the text).&quot;<br>
<br>
        ((characterIndex ~= nil<br>
                and: [characterIndex &gt; text size])<br>
                        or: [(line last = text size)<br>
                                and: [(destY + line lineHeight) &lt; characterPoint y]])<br>
                ifTrue: [&quot;When off end of string, give data for next character&quot;<br>
                                destY := destY +  line lineHeight.<br>
                                characterPoint := (nextLeftMargin ifNil: [leftMargin]) @ destY.<br>
                                (lastIndex &lt; text size and: [(text at: lastIndex) = CR and: [(text at: lastIndex+1) = Character lf]])<br>
                                        ifTrue: [lastIndex := lastIndex + 2]<br>
                                        ifFalse: [lastIndex := lastIndex + 1].<br>
+                               lastCharacterWidth := 0.<br>
-                               self lastCharacterExtentSetX: 0.<br>
                                ^ true].<br>
                characterPoint := destX @ destY.<br>
+               lastCharacterWidth := rightMargin - destX.<br>
-               self lastCharacterExtentSetX: rightMargin - destX.<br>
                ^true!<br>
<br>
Item was changed:<br>
  ----- Method: CharacterBlockScanner&gt;&gt;crossedX (in category &#39;stop conditions&#39;) -----<br>
  crossedX<br>
        &quot;Text display has wrapping. The scanner just found a character past the x<br>
        location of the cursor. We know that the cursor is pointing at a character<br>
        or before one.&quot;<br>
<br>
+       self retrieveLastCharacterWidth.<br>
+<br>
-       | currentX lastCharacter |<br>
        characterIndex == nil ifFalse: [<br>
                &quot;If the last character of the last line is a space,<br>
                and it crosses the right margin, then locating<br>
                the character block after it is impossible without this hack.&quot;<br>
                characterIndex &gt; text size ifTrue: [<br>
                        lastIndex := characterIndex.<br>
                        characterPoint := (nextLeftMargin ifNil: [leftMargin]) @ (destY + line lineHeight).<br>
                        ^true]].<br>
+       characterPoint x &lt;= (destX + (lastCharacterWidth // 2))<br>
-       characterPoint x &lt;= (destX + (lastCharacterExtent x // 2))<br>
                ifTrue: [characterPoint := destX @ destY.<br>
                                ^true].<br>
        lastIndex &gt;= line last<br>
                ifTrue: [characterPoint := destX @ destY.<br>
                                ^true].<br>
<br>
        &quot;Pointing past middle of a character, return the next character.&quot;<br>
        lastIndex := lastIndex + 1.<br>
+       characterPoint := destX + lastCharacterWidth + kern @ destY.<br>
-       lastCharacter := text at: lastIndex.<br>
-       currentX := destX + lastCharacterExtent x + kern.<br>
-       self lastCharacterExtentSetX: (font widthOf: lastCharacter).<br>
-       characterPoint := currentX @ destY.<br>
-       lastCharacter = Space ifFalse: [^ true].<br>
-<br>
-       &quot;Yukky if next character is space or tab.&quot;<br>
-       alignment = Justified ifTrue:<br>
-               [self lastCharacterExtentSetX:<br>
-                       (lastCharacterExtent x +        (line justifiedPadFor: (spaceCount + 1) font: font))].<br>
-<br>
        ^ true!<br>
<br>
Item was changed:<br>
  ----- Method: CharacterBlockScanner&gt;&gt;endOfRun (in category &#39;stop conditions&#39;) -----<br>
  endOfRun<br>
        &quot;Before arriving at the cursor location, the selection has encountered an<br>
        end of run. Answer false if the selection continues, true otherwise. Set<br>
        up indexes for building the appropriate CharacterBlock.&quot;<br>
<br>
+       | runLength lineStop |<br>
+<br>
-       | runLength lineStop lastCharacter |<br>
        (((characterIndex ~~ nil and:<br>
                [runStopIndex &lt; characterIndex and: [runStopIndex &lt; text size]])<br>
                        or:     [characterIndex == nil and: [lastIndex &lt; line last]]) or: [<br>
                                ((lastIndex &lt; line last)<br>
                                and: [((text at: lastIndex) leadingChar ~= (text at: lastIndex+1) leadingChar)<br>
                                        and: [lastIndex ~= characterIndex]])])<br>
                ifTrue: [&quot;We&#39;re really at the end of a real run.&quot;<br>
+                               runLength := text runLengthFor: (lastIndex := lastIndex + 1).<br>
+                               lineStop := characterIndex      &quot;scanning for index&quot;<br>
+                                               ifNil: [line last].             &quot;scanning for point&quot;.<br>
-                               runLength := (text runLengthFor: (lastIndex := lastIndex + 1)).<br>
-                               characterIndex ~~ nil<br>
-                                       ifTrue: [lineStop := characterIndex     &quot;scanning for index&quot;]<br>
-                                       ifFalse:        [lineStop := line last                  &quot;scanning for point&quot;].<br>
                                (runStopIndex := lastIndex + (runLength - 1)) &gt; lineStop<br>
+                                       ifTrue: [runStopIndex := lineStop].<br>
-                                       ifTrue:         [runStopIndex := lineStop].<br>
                                self setStopConditions.<br>
                                ^false].<br>
<br>
+       self retrieveLastCharacterWidth.<br>
+<br>
+       (characterIndex == nil and: [lastIndex = line last])<br>
+               ifTrue: [characterPoint x &gt; (destX + (lastCharacterWidth // 2))<br>
+                       ifTrue:<br>
+                               [ &quot;Correct for clicking right half of last character in line<br>
+                               means selecting AFTER the char&quot;<br>
+                               lastIndex := lastIndex + 1.<br>
+                               lastCharacterWidth := 0.<br>
+                               characterPoint := destX + lastCharacterWidth @ destY.<br>
+                               ^true]].<br>
+<br>
-       lastCharacter := text at: lastIndex.<br>
        characterPoint := destX @ destY.<br>
-       ((lastCharacter = Space and: [alignment = Justified])<br>
-               or: [lastCharacter = Tab and: [lastSpaceOrTabExtent notNil]])<br>
-               ifTrue: [lastCharacterExtent := lastSpaceOrTabExtent].<br>
        characterIndex ~~ nil<br>
                ifTrue: [&quot;If scanning for an index and we&#39;ve stopped on that index,<br>
                                then we back destX off by the width of the character stopped on<br>
                                (it will be pointing at the right side of the character) and return&quot;<br>
                                runStopIndex = characterIndex<br>
+                                       ifTrue: [characterPoint := destX - lastCharacterWidth @ destY.<br>
-                                       ifTrue: [self characterPointSetX: destX - lastCharacterExtent x.<br>
                                                        ^true].<br>
                                &quot;Otherwise the requested index was greater than the length of the<br>
                                string.  Return string size + 1 as index, indicate further that off the<br>
                                string by setting character to nil and the extent to 0.&quot;<br>
                                lastIndex :=  lastIndex + 1.<br>
+                               lastCharacterWidth := 0.<br>
-                               self lastCharacterExtentSetX: 0.<br>
                                ^true].<br>
<br>
        &quot;Scanning for a point and either off the end of the line or off the end of the string.&quot;<br>
        runStopIndex = text size<br>
                ifTrue: [&quot;off end of string&quot;<br>
                                lastIndex :=  lastIndex + 1.<br>
+                               lastCharacterWidth := 0.<br>
-                               self lastCharacterExtentSetX: 0.<br>
                                ^true].<br>
        &quot;just off end of line without crossing x&quot;<br>
        lastIndex := lastIndex + 1.<br>
        ^true!<br>
<br>
Item was removed:<br>
- ----- Method: CharacterBlockScanner&gt;&gt;lastCharacterExtentSetX: (in category &#39;private&#39;) -----<br>
- lastCharacterExtentSetX: xVal<br>
-       lastCharacterExtent := xVal @ lastCharacterExtent y!<br>
<br>
Item was removed:<br>
- ----- Method: CharacterBlockScanner&gt;&gt;lastSpaceOrTabExtentSetX: (in category &#39;private&#39;) -----<br>
- lastSpaceOrTabExtentSetX: xVal<br>
-       lastSpaceOrTabExtent := xVal @ lastSpaceOrTabExtent y!<br>
<br>
Item was changed:<br>
  ----- Method: CharacterBlockScanner&gt;&gt;paddedSpace (in category &#39;stop conditions&#39;) -----<br>
  paddedSpace<br>
        &quot;When the line is justified, the spaces will not be the same as the font&#39;s<br>
        space character. A padding of extra space must be considered in trying<br>
        to find which character the cursor is pointing at. Answer whether the<br>
        scanning has crossed the cursor.&quot;<br>
<br>
        | pad |<br>
-       pad := 0.<br>
        spaceCount := spaceCount + 1.<br>
        pad := line justifiedPadFor: spaceCount font: font.<br>
+       lastCharacterWidth := spaceWidth + pad.<br>
+       (destX + lastCharacterWidth)  &gt;= characterPoint x<br>
+               ifTrue:<br>
+                       [^self crossedX].<br>
-       lastSpaceOrTabExtent := lastCharacterExtent.<br>
-       self lastSpaceOrTabExtentSetX:  spaceWidth + pad.<br>
-       (destX + lastSpaceOrTabExtent x)  &gt;= characterPoint x<br>
-               ifTrue: [lastCharacterExtent := lastSpaceOrTabExtent.<br>
-                               ^self crossedX].<br>
        lastIndex := lastIndex + 1.<br>
+       destX := destX + lastCharacterWidth.<br>
-       destX := destX + lastSpaceOrTabExtent x.<br>
        ^ false<br>
  !<br>
<br>
Item was added:<br>
+ ----- Method: CharacterBlockScanner&gt;&gt;retrieveLastCharacterWidth (in category &#39;private&#39;) -----<br>
+ retrieveLastCharacterWidth<br>
+       | lastCharacter |<br>
+       lastIndex &gt; text size ifTrue: [^lastCharacterWidth := 0].<br>
+       lastCharacter := text at: lastIndex.<br>
+       (lastCharacter charCode &gt;= 256 or: [(stopConditions at: lastCharacter charCode + 1) isNil])<br>
+               ifTrue: [lastCharacterWidth := font widthOf: (text at: lastIndex)].<br>
+       &quot;if last character was a stop condition, then the width is already set&quot;<br>
+       ^lastCharacterWidth!<br>
<br>
Item was changed:<br>
  ----- Method: CharacterBlockScanner&gt;&gt;space (in category &#39;stop conditions&#39;) -----<br>
  space<br>
        &quot;Account for spaceWidth&quot;<br>
<br>
        spaceCount := spaceCount + 1.<br>
+       lastCharacterWidth := spaceWidth.<br>
+       (destX + lastCharacterWidth)  &gt;= characterPoint x<br>
+               ifTrue:<br>
+                       [^self crossedX].<br>
-       lastSpaceOrTabExtent := lastCharacterExtent.<br>
-       self lastSpaceOrTabExtentSetX:  spaceWidth.<br>
-       (destX + spaceWidth)  &gt;= characterPoint x<br>
-               ifTrue: [lastCharacterExtent := lastSpaceOrTabExtent.<br>
-                               ^self crossedX].<br>
        lastIndex := lastIndex + 1.<br>
+       destX := destX + lastCharacterWidth.<br>
+       ^ false!<br>
-       destX := destX + spaceWidth.<br>
-       ^ false<br>
- !<br>
<br>
Item was changed:<br>
  ----- Method: CharacterBlockScanner&gt;&gt;tab (in category &#39;stop conditions&#39;) -----<br>
  tab<br>
        | currentX |<br>
        currentX := (alignment = Justified and: [self leadingTab not])<br>
                ifTrue:         &quot;imbedded tabs in justified text are weird&quot;<br>
                        [destX + (textStyle tabWidth - (line justifiedTabDeltaFor: spaceCount)) max: destX]<br>
                ifFalse:<br>
                        [textStyle<br>
                                nextTabXFrom: destX<br>
                                leftMargin: leftMargin<br>
                                rightMargin: rightMargin].<br>
+       lastCharacterWidth := currentX - destX max: 0.<br>
-       lastSpaceOrTabExtent := lastCharacterExtent.<br>
-       self lastSpaceOrTabExtentSetX: (currentX - destX max: 0).<br>
        currentX &gt;= characterPoint x<br>
                ifTrue:<br>
+                       [^ self crossedX].<br>
-                       [lastCharacterExtent := lastSpaceOrTabExtent.<br>
-                       ^ self crossedX].<br>
        destX := currentX.<br>
        lastIndex := lastIndex + 1.<br>
        ^false!<br>
<br>
<br>
</blockquote></div><br></div>