[squeak-dev] The Trunk: Graphics-nice.243.mcz

Nicolas Cellier nicolas.cellier.aka.nice at gmail.com
Wed Oct 2 01:03:08 UTC 2013


By the way, I broke the CharacterBlockScanner workaround which was aimed at
st80 case only, phew...
After getting rid of maintenance of Single and Multi Scanner hierarchies, I
still have to handle New and old Paragraph...


2013/10/2 Nicolas Cellier <nicolas.cellier.aka.nice at gmail.com>

> And I started to write CharacterScannerTest and found the excellent
> TestIndenting, for ST80 only...
> Ah, so we even have tests, just need to complete them a bit.
>
>
> 2013/10/2 Nicolas Cellier <nicolas.cellier.aka.nice at gmail.com>
>
>> Ah, to be complete I forgot to say farewell multiComposeSomething, since
>> it was an unused sender of fixupLastLineIfCR that I wanted to remove too...
>>
>> If you want to see the bug before this change, then:
>> open a Workspace, type M space space space ...
>> .. until the cursor wraps to... the beginning of first line.
>> type one more space and it skips on the next line.
>>
>> What happens it's damn simple:
>> - the composition scanner crossedX
>> - the space that crossedX is inserted in the first line (like a CR would)
>> - there is no more character in the text
>> - the TextComposer has finished to composeLines and ^nil
>> - it tries to fixupLastLineIfCR, unnfortunately last char behaved as a CR
>> but wasn't a CR
>>
>> Composition finishes with a single line when there should be logically an
>> empty second line...
>>
>> Chris: I hope the comments carry enough intention :)
>>
>>
>> 2013/10/2 <commits at source.squeak.org>
>>
>> Nicolas Cellier uploaded a new version of Graphics to project The Trunk:
>>> http://source.squeak.org/trunk/Graphics-nice.243.mcz
>>>
>>> ==================== Summary ====================
>>>
>>> Name: Graphics-nice.243
>>> Author: nice
>>> Time: 2 October 2013, 2:47:50.925 am
>>> UUID: 220dcb9f-1fed-4f7d-b6de-170fe2b2a3ce
>>> Ancestors: Graphics-nice.242
>>>
>>> Fix a composition glitch when the last character of a text is a space
>>> that crosses the right margin boundary.
>>> In such case, a virtual empty line must be added to the composition in
>>> order to correctly materialize text selection and cursor position, and so
>>> as to continue typing on next line.
>>>
>>> The case when last character is a carriage return is in all point
>>> similar.
>>> Indeed, a space that crossedX is visually turned into a new line.
>>> TextComposer previously tried to reverse engineer scanner's work to
>>> recognize the CR case, which is a smell.
>>> This change unifies handling for the two cases by rather asking to the
>>> scanner doesTheLineBreaksAfterLastChar?
>>> Remove fixupLastLineIfCR which is tainted with half case only.
>>> Remove the workaround in CharacterBlockScanner that did not work around
>>> anything.
>>>
>>> Fix the breaking at non space for eastern asia:
>>> 1) registerBreakableIndex records that the line can wrap before the
>>> current character, and spaceIndex was pointing at this character that will
>>> potentially wrap on next line.
>>> 2) It is still possible to apply Justified alignment based on space
>>> adjustment if some spaces are used in the text, so correctly set the line
>>> spaceCount and paddingWidth.
>>>
>>> =============== Diff against Graphics-nice.242 ===============
>>>
>>> Item was changed:
>>>   ----- Method: CharacterBlockScanner>>crossedX (in category 'stop
>>> conditions') -----
>>>   crossedX
>>>         "Text display has wrapping. The scanner just found a character
>>> past the x
>>>         location of the cursor. We know that the cursor is pointing at a
>>> character
>>>         or before one."
>>>
>>>         self retrieveLastCharacterWidth.
>>>
>>> -       characterIndex == nil ifFalse: [
>>> -               "If the last character of the last line is a space,
>>> -               and it crosses the right margin, then locating
>>> -               the character block after it is impossible without this
>>> hack."
>>> -               characterIndex > text size ifTrue: [
>>> -                       lastIndex := characterIndex.
>>> -                       characterPoint := (nextLeftMargin ifNil:
>>> [leftMargin]) @ (destY + line lineHeight).
>>> -                       ^true]].
>>>         characterPoint x <= (destX + (lastCharacterWidth // 2))
>>>                 ifTrue: [characterPoint := destX @ destY.
>>>                                 ^true].
>>>         lastIndex >= line last
>>>                 ifTrue: [characterPoint := destX @ destY.
>>>                                 ^true].
>>>
>>>         "Pointing past middle of a character, return the next character."
>>>         lastIndex := lastIndex + 1.
>>>         characterPoint := destX + lastCharacterWidth + kern @ destY.
>>>         ^ true!
>>>
>>> Item was changed:
>>>   CharacterScanner subclass: #CompositionScanner
>>> +       instanceVariableNames: 'spaceX spaceIndex lineHeight baseline
>>> lineHeightAtSpace baselineAtSpace lastBreakIsNotASpace
>>> nextIndexAfterLineBreak'
>>> -       instanceVariableNames: 'spaceX spaceIndex lineHeight baseline
>>> lineHeightAtSpace baselineAtSpace lastBreakIsNotASpace'
>>>         classVariableNames: ''
>>>         poolDictionaries: ''
>>>         category: 'Graphics-Text'!
>>>
>>>   !CompositionScanner commentStamp: '<historical>' prior: 0!
>>>   CompositionScanners are used to measure text and determine where line
>>> breaks and space padding should occur.!
>>>
>>> Item was changed:
>>>   ----- Method:
>>> CompositionScanner>>composeFrom:inRectangle:firstLine:leftSide:rightSide:
>>> (in category 'scanning') -----
>>>   composeFrom: startIndex inRectangle: lineRectangle
>>>         firstLine: firstLine leftSide: leftSide rightSide: rightSide
>>>         "Answer an instance of TextLineInterval that represents the next
>>> line in the paragraph."
>>>         | runLength stopCondition |
>>>         "Set up margins"
>>>         leftMargin := lineRectangle left.
>>>         leftSide ifTrue: [leftMargin := leftMargin +
>>>                                                 (firstLine ifTrue:
>>> [textStyle firstIndent]
>>>                                                                 ifFalse:
>>> [textStyle restIndent])].
>>>         destX := spaceX := leftMargin.
>>>         rightMargin := lineRectangle right.
>>>         rightSide ifTrue: [rightMargin := rightMargin - textStyle
>>> rightIndent].
>>>         lastIndex := startIndex.        "scanning sets last index"
>>>         destY := lineRectangle top.
>>>         lineHeight := baseline := 0.  "Will be increased by setFont"
>>>         line := (TextLine start: lastIndex stop: 0 internalSpaces: 0
>>> paddingWidth: 0)
>>>                                 rectangle: lineRectangle.
>>>         self setStopConditions. "also sets font"
>>>         runLength := text runLengthFor: startIndex.
>>>         runStopIndex := (lastIndex := startIndex) + (runLength - 1).
>>> +       nextIndexAfterLineBreak := spaceCount := 0.
>>> -       spaceCount := 0.
>>>         lastBreakIsNotASpace := false.
>>>         self handleIndentation.
>>>         leftMargin := destX.
>>>         line leftMargin: leftMargin.
>>>
>>>         [stopCondition := self scanCharactersFrom: lastIndex to:
>>> runStopIndex
>>>                 in: text string rightX: rightMargin stopConditions:
>>> stopConditions
>>>                 kern: kern.
>>>         "See setStopConditions for stopping conditions for composing."
>>>         self perform: stopCondition] whileFalse.
>>>
>>>         ^ line
>>>                 lineHeight: lineHeight + textStyle leading
>>>                 baseline: baseline + textStyle leading!
>>>
>>> Item was changed:
>>>   ----- Method:
>>> CompositionScanner>>composeLine:fromCharacterIndex:inParagraph: (in
>>> category 'scanning') -----
>>>   composeLine: lineIndex fromCharacterIndex: startIndex inParagraph:
>>> aParagraph
>>>         "Answer an instance of TextLineInterval that represents the next
>>> line in the paragraph."
>>>         | runLength stopCondition |
>>>         destX := spaceX := leftMargin := aParagraph
>>> leftMarginForCompositionForLine: lineIndex.
>>>         destY := 0.
>>>         rightMargin := aParagraph rightMarginForComposition.
>>>         leftMargin >= rightMargin ifTrue: [self error: 'No room between
>>> margins to compose'].
>>>         lastIndex := startIndex.        "scanning sets last index"
>>>         lineHeight := textStyle lineGrid.  "may be increased by
>>> setFont:..."
>>>         baseline := textStyle baseline.
>>>         self setStopConditions. "also sets font"
>>>         self handleIndentation.
>>>         runLength := text runLengthFor: startIndex.
>>>         runStopIndex := (lastIndex := startIndex) + (runLength - 1).
>>>         line := TextLineInterval
>>>                 start: lastIndex
>>>                 stop: 0
>>>                 internalSpaces: 0
>>>                 paddingWidth: 0.
>>> +       nextIndexAfterLineBreak := spaceCount := 0.
>>> -       spaceCount := 0.
>>>         lastBreakIsNotASpace := false.
>>>
>>>         [stopCondition := self scanCharactersFrom: lastIndex to:
>>> runStopIndex
>>>                 in: text string rightX: rightMargin stopConditions:
>>> stopConditions
>>>                 kern: kern.
>>>         "See setStopConditions for stopping conditions for composing."
>>>         self perform: stopCondition] whileFalse.
>>>
>>>         ^line
>>>                 lineHeight: lineHeight + textStyle leading
>>>                 baseline: baseline + textStyle leading!
>>>
>>> Item was changed:
>>>   ----- Method: CompositionScanner>>cr (in category 'stop conditions')
>>> -----
>>>   cr
>>>         "Answer true. Set up values for the text line interval currently
>>> being
>>>         composed."
>>>
>>>         pendingKernX := 0.
>>>         (lastIndex < text size and: [(text at: lastIndex) = CR and:
>>> [(text at: lastIndex+1) = Character lf]]) ifTrue: [lastIndex := lastIndex +
>>> 1].
>>>         line stop: lastIndex.
>>> +       nextIndexAfterLineBreak := lastIndex + 1.
>>>         spaceX := destX.
>>>         lastBreakIsNotASpace := false.
>>>         line paddingWidth: rightMargin - spaceX.
>>>         ^true!
>>>
>>> Item was changed:
>>>   ----- Method: CompositionScanner>>crossedX (in category 'stop
>>> conditions') -----
>>>   crossedX
>>>         "There is a word that has fallen across the right edge of the
>>> composition
>>>         rectangle. This signals the need for wrapping which is done to
>>> the last
>>>         space that was encountered, as recorded by the space stop
>>> condition,
>>>         or any other breakable character if the language permits so."
>>>
>>>         pendingKernX := 0.
>>>
>>>         lastBreakIsNotASpace ifTrue:
>>> +               ["In some languages break is possible before a non
>>> space."
>>> +               nextIndexAfterLineBreak := spaceIndex.
>>> +               line stop: spaceIndex - 1.
>>> -               ["In some languages break is possible on non space."
>>> -               line stop: spaceIndex.
>>>                 lineHeight := lineHeightAtSpace.
>>>                 baseline := baselineAtSpace.
>>> +               line paddingWidth: rightMargin - spaceX.
>>> -               spaceCount := spaceCount - 1.
>>> -               spaceIndex := spaceIndex - 1.
>>> -               line paddingWidth: rightMargin.
>>>                 line internalSpaces: spaceCount.
>>>                 ^true].
>>>
>>>         spaceCount >= 1 ifTrue:
>>>                 ["The common case. First back off to the space at which
>>> we wrap."
>>>                 line stop: spaceIndex.
>>> +               nextIndexAfterLineBreak := spaceIndex + 1.
>>>                 lineHeight := lineHeightAtSpace.
>>>                 baseline := baselineAtSpace.
>>>                 spaceCount := spaceCount - 1.
>>>                 spaceIndex := spaceIndex - 1.
>>>
>>>                 "Check to see if any spaces preceding the one at which
>>> we wrap.
>>>                         Double space after punctuation, most likely."
>>>                 [(spaceCount > 1 and: [(text at: spaceIndex) = Space])]
>>>                         whileTrue:
>>>                                 [spaceCount := spaceCount - 1.
>>>                                 "Account for backing over a run which
>>> might
>>>                                         change width of space."
>>>                                 font := text fontAt: spaceIndex
>>> withStyle: textStyle.
>>>                                 spaceIndex := spaceIndex - 1.
>>>                                 spaceX := spaceX - (font widthOf:
>>> Space)].
>>>                 line paddingWidth: rightMargin - spaceX.
>>>                 line internalSpaces: spaceCount]
>>>         ifFalse:
>>>                 ["Neither internal nor trailing spaces -- almost never
>>> happens."
>>>                 lastIndex := lastIndex - 1.
>>>                 [destX <= rightMargin or: [ lastIndex = 0 ]]
>>>                         whileFalse:
>>>                                 [destX := destX - (font widthOf: (text
>>> at: lastIndex)).
>>>                                 lastIndex := lastIndex - 1].
>>> +               nextIndexAfterLineBreak := lastIndex + 1.
>>>                 spaceX := destX.
>>>                 line paddingWidth: rightMargin - destX.
>>>                 line stop: (lastIndex max: line first)].
>>>         ^true!
>>>
>>> Item was added:
>>> + ----- Method: CompositionScanner>>doesTheLineBreaksAfterLastChar (in
>>> category 'accessing') -----
>>> + doesTheLineBreaksAfterLastChar
>>> +       ^nextIndexAfterLineBreak > text size!
>>>
>>> Item was changed:
>>>   ----- Method:
>>> TextComposer>>composeLinesFrom:to:delta:into:priorLines:atY:textStyle:text:container:wantsColumnBreaks:
>>> (in category 'as yet unclassified') -----
>>>   composeLinesFrom: argStart to: argStop delta: argDelta into:
>>> argLinesCollection priorLines: argPriorLines atY: argStartY textStyle:
>>> argTextStyle text: argText container: argContainer wantsColumnBreaks:
>>> argWantsColumnBreaks
>>>
>>>         wantsColumnBreaks := argWantsColumnBreaks.
>>>         lines := argLinesCollection.
>>>         theTextStyle := argTextStyle.
>>>         theText := argText.
>>>         theContainer := argContainer.
>>>         deltaCharIndex := argDelta.
>>>         currCharIndex := startCharIndex := argStart.
>>>         stopCharIndex := argStop.
>>>         prevLines := argPriorLines.
>>>         currentY := argStartY.
>>>         maxRightX := theContainer left.
>>>         possibleSlide := stopCharIndex < theText size and: [theContainer
>>> isMemberOf: Rectangle].
>>>         nowSliding := false.
>>>         prevIndex := 1.
>>>         "choose an appropriate scanner - should go away soon, when they
>>> can be unified"
>>>         scanner := CompositionScanner new.
>>>         scanner text: theText textStyle: theTextStyle.
>>>         scanner wantsColumnBreaks: wantsColumnBreaks.
>>>         defaultLineHeight := scanner computeDefaultLineHeight.
>>>         isFirstLine := true.
>>>         self composeAllLines.
>>>         isFirstLine ifTrue: ["No space in container or empty text"
>>>                 self
>>>                         addNullLineWithIndex: startCharIndex
>>>                         andRectangle: (theContainer topLeft extent:
>>> 0 at defaultLineHeight)
>>>         ] ifFalse: [
>>> +               (lines last last = theText size and: [scanner
>>> doesTheLineBreaksAfterLastChar])
>>> +                       ifTrue: [self addNullLineForIndex: theText size
>>> + 1]
>>> -               self fixupLastLineIfCR
>>>         ].
>>>         ^{lines asArray. maxRightX}
>>> -
>>>   !
>>>
>>> Item was removed:
>>> - ----- Method: TextComposer>>fixupLastLineIfCR (in category 'as yet
>>> unclassified') -----
>>> - fixupLastLineIfCR
>>> - "This awful bit is to ensure that if we have scanned all the text and
>>> the last character is a CR that there is a null line at the end of lines.
>>> Sometimes this was not happening which caused anomalous selections when
>>> selecting all the text. This is implemented as a post-composition fixup
>>> because I couldn't figure out where to put it in the main logic."
>>> -
>>> -       (theText size > 0 and: [CharacterSet crlf includes: theText
>>> last]) ifFalse: [^self].
>>> -       self addNullLineForIndex: theText size + 1.
>>> - !
>>>
>>> Item was removed:
>>> - ----- Method:
>>> TextComposer>>multiComposeLinesFrom:to:delta:into:priorLines:atY:textStyle:text:container:wantsColumnBreaks:
>>> (in category 'as yet unclassified') -----
>>> - multiComposeLinesFrom: argStart to: argStop delta: argDelta into:
>>> argLinesCollection priorLines: argPriorLines atY: argStartY textStyle:
>>> argTextStyle text: argText container: argContainer wantsColumnBreaks:
>>> argWantsColumnBreaks
>>> -
>>> - "temporarily add this here to support move to drop MultiTextComposer"
>>> - "now redundant and ready to remove later"
>>> -       wantsColumnBreaks := argWantsColumnBreaks.
>>> -       lines := argLinesCollection.
>>> -       theTextStyle := argTextStyle.
>>> -       theText := argText.
>>> -       theContainer := argContainer.
>>> -       deltaCharIndex := argDelta.
>>> -       currCharIndex := startCharIndex := argStart.
>>> -       stopCharIndex := argStop.
>>> -       prevLines := argPriorLines.
>>> -       currentY := argStartY.
>>> -       maxRightX := theContainer left.
>>> -       possibleSlide := stopCharIndex < theText size and: [theContainer
>>> isMemberOf: Rectangle].
>>> -       nowSliding := false.
>>> -       prevIndex := 1.
>>> -       scanner := CompositionScanner new text: theText textStyle:
>>> theTextStyle.
>>> -       scanner wantsColumnBreaks: wantsColumnBreaks.
>>> -       defaultLineHeight := scanner computeDefaultLineHeight.
>>> -       isFirstLine := true.
>>> -       self composeAllLines.
>>> -       isFirstLine ifTrue: ["No space in container or empty text"
>>> -               self
>>> -                       addNullLineWithIndex: startCharIndex
>>> -                       andRectangle: (theContainer topLeft extent:
>>> 0 at defaultLineHeight)
>>> -       ] ifFalse: [
>>> -               self fixupLastLineIfCR
>>> -       ].
>>> -       ^{lines asArray. maxRightX}
>>> -
>>> - !
>>>
>>>
>>>
>>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.squeakfoundation.org/pipermail/squeak-dev/attachments/20131002/544ac686/attachment.htm


More information about the Squeak-dev mailing list