Nicolas Cellier uploaded a new version of Graphics to project The Trunk:
http://source.squeak.org/trunk/Graphics-nice.237.mcz
==================== Summary ====================
Name: Graphics-nice.237
Author: nice
Time: 29 September 2013, 4:26:28.942 pm
UUID: f73a5da8-0cca-42c7-a0b3-08472216f2cf
Ancestors: Graphics-nice.236
Copy the multi-lingual methods from MultiCharacterScanner hierarchy that were missing in CharacterScanner hierarchy.
Note that this does not include the precomposed unicode Combining which deserves a rewrite.
Also note that non-space breakable chars are handled a bit differently in CompositionScanner in order to make the minimal change that could work.
Start using the CharacterScanner hierarchy instead of the Multi one.
=============== Diff against Graphics-nice.236 ===============
Item was added:
+ ----- Method: CharacterBlockScanner>>theFirstCharCrossedX (in category 'private') -----
+ theFirstCharCrossedX
+ "Note: it is hard to explain why, but this is required to keep selection of leftmost char possible."
+ ^false!
Item was added:
+ ----- Method: CharacterScanner>>isBreakableAt:in:in: (in category 'scanner methods') -----
+ isBreakableAt: index in: sourceString in: encodingClass
+ "check with the encoding whether the character at index is a breakable character- only the JISX0208 & JapaneseEnvironment will ever return true, so only the scanJapaneseCharacters... method calls this"
+ ^ encodingClass isBreakableAt: index in: sourceString.
+ !
Item was removed:
- ----- Method: CharacterScanner>>isBreakableAtIndex: (in category 'scanner methods') -----
- isBreakableAtIndex: index
- "appears t obe unused - cf MultiCharacterScanner>isBreakableAt:in:in"
- ^ (EncodedCharSet at: ((text at: index) leadingChar + 1)) isBreakableAt: index in: text.
- !
Item was added:
+ ----- Method: CharacterScanner>>registerBreakableIndex (in category 'multilingual scanning') -----
+ registerBreakableIndex
+
+ "Record left x and character index of the line-wrappable point.
+ The default implementation here does nothing."
+
+ ^ false.
+ !
Item was changed:
----- Method: CharacterScanner>>scanJapaneseCharactersFrom:to:in:rightX:stopConditions:kern: (in category 'scanner methods') -----
scanJapaneseCharactersFrom: startIndex to: stopIndex in: sourceString rightX: rightX stopConditions: stops kern: kernDelta
| ascii encoding nextDestX startEncoding |
lastIndex := startIndex.
lastIndex > stopIndex ifTrue: [lastIndex := stopIndex. ^ stops endOfRun].
startEncoding := (sourceString at: startIndex) leadingChar.
[lastIndex <= stopIndex] whileTrue: [
encoding := (sourceString at: lastIndex) leadingChar.
encoding ~= startEncoding ifTrue: [lastIndex := lastIndex - 1. ^ stops endOfRun].
ascii := (sourceString at: lastIndex) charCode.
(encoding = 0 and: [ascii < 256 and:[(stops at: ascii + 1) notNil]])
ifTrue: [^ stops at: ascii + 1].
+ (self isBreakableAt: lastIndex in: sourceString in: (EncodedCharSet charsetAt: encoding)) ifTrue: [
+ self registerBreakableIndex.
+ ].
nextDestX := destX + (font widthOf: (sourceString at: lastIndex)).
+ nextDestX > rightX ifTrue: [self theFirstCharCrossedX ifFalse: [^ stops crossedX]].
- nextDestX > rightX ifTrue: [^ stops crossedX].
destX := nextDestX + kernDelta.
- "destX printString displayAt: 0@(lastIndex*20)."
lastIndex := lastIndex + 1.
].
lastIndex := stopIndex.
^ stops endOfRun!
Item was changed:
----- Method: CharacterScanner>>scanMultiCharactersFrom:to:in:rightX:stopConditions:kern: (in category 'scanning') -----
scanMultiCharactersFrom: startIndex to: stopIndex in: sourceString rightX: rightX stopConditions: stops kern: kernDelta
+ | ascii encoding nextDestX startEncoding floatDestX widthAndKernedWidth nextChar atEndOfRun |
- | ascii encoding nextDestX startEncoding |
lastIndex := startIndex.
lastIndex > stopIndex ifTrue: [lastIndex := stopIndex. ^ stops endOfRun].
startEncoding := (sourceString at: startIndex) leadingChar.
+ floatDestX := destX.
+ widthAndKernedWidth := Array new: 2.
+ atEndOfRun := false.
[lastIndex <= stopIndex] whileTrue: [
encoding := (sourceString at: lastIndex) leadingChar.
encoding ~= startEncoding ifTrue: [lastIndex := lastIndex - 1. ^ stops endOfRun].
ascii := (sourceString at: lastIndex) charCode.
+ (ascii < 256 and: [(stops at: ascii + 1) ~~ nil]) ifTrue: [^ stops at: ascii + 1].
+ nextChar := (lastIndex + 1 <= stopIndex)
+ ifTrue:[sourceString at: lastIndex + 1]
+ ifFalse:[
+ atEndOfRun := true.
+ "if there is a next char in sourceString, then get the kern
+ and store it in pendingKernX"
+ lastIndex + 1 <= sourceString size
+ ifTrue:[sourceString at: lastIndex + 1]
+ ifFalse:[ nil]].
+ font
+ widthAndKernedWidthOfLeft: (sourceString at: lastIndex)
+ right: nextChar
+ into: widthAndKernedWidth.
+ nextDestX := floatDestX + (widthAndKernedWidth at: 1).
+ nextDestX > rightX ifTrue: [self theFirstCharCrossedX ifFalse: [^stops crossedX]].
+ floatDestX := floatDestX + kernDelta + (widthAndKernedWidth at: 2).
+ atEndOfRun
+ ifTrue:[
+ pendingKernX := (widthAndKernedWidth at: 2) - (widthAndKernedWidth at: 1).
+ floatDestX := floatDestX - pendingKernX].
+ destX := floatDestX .
- (encoding = 0 and: [ascii < 256 and:[(stops at: ascii + 1) notNil]])
- ifTrue: [^ stops at: ascii + 1].
- nextDestX := destX + (font widthOf: (sourceString at: lastIndex)).
- nextDestX > rightX ifTrue: [^ stops crossedX].
- destX := nextDestX + kernDelta.
- "destX printString displayAt: 0@(lastIndex*20)."
lastIndex := lastIndex + 1.
].
lastIndex := stopIndex.
^ stops endOfRun!
Item was added:
+ ----- Method: CharacterScanner>>theFirstCharCrossedX (in category 'private') -----
+ theFirstCharCrossedX
+ "Tell whether the left most char crossed the right margin boundary"
+ ^destX = leftMargin!
Item was changed:
CharacterScanner subclass: #CompositionScanner
+ instanceVariableNames: 'spaceX spaceIndex lineHeight baseline lineHeightAtSpace baselineAtSpace lastBreakIsNotASpace'
- instanceVariableNames: 'spaceX spaceIndex lineHeight baseline lineHeightAtSpace baselineAtSpace'
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>>columnBreak (in category 'stop conditions') -----
columnBreak
"Answer true. Set up values for the text line interval currently being
composed."
pendingKernX := 0.
line stop: lastIndex.
spaceX := destX.
+ lastBreakIsNotASpace := false.
line paddingWidth: rightMargin - spaceX.
^true!
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).
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.
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.
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."
- space that was encountered, as recorded by the space stop condition."
pendingKernX := 0.
+
+ lastBreakIsNotASpace ifTrue:
+ ["In some languages break is possible on non space."
+ line stop: spaceIndex.
+ lineHeight := lineHeightAtSpace.
+ baseline := baselineAtSpace.
+ 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.
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].
spaceX := destX.
line paddingWidth: rightMargin - destX.
line stop: (lastIndex max: line first)].
^true!
Item was added:
+ ----- Method: CompositionScanner>>registerBreakableIndex (in category 'multilingual scanning') -----
+ registerBreakableIndex
+ "Record left x and character index of the line-wrappable point.
+ Used for wrap-around in eastern Asian languages."
+
+ spaceX := destX.
+ lineHeightAtSpace := lineHeight.
+ baselineAtSpace := baseline.
+ spaceIndex := lastIndex.
+ lastBreakIsNotASpace := true.!
Item was changed:
----- Method: CompositionScanner>>space (in category 'stop conditions') -----
space
+ "Record left x and character index of the space character just encountered.
- "Record left x and character index of the space character just encounted.
Used for wrap-around. Answer whether the character has crossed the
right edge of the composition rectangle of the paragraph."
pendingKernX := 0.
spaceX := destX.
destX := spaceX + spaceWidth.
spaceIndex := lastIndex.
lineHeightAtSpace := lineHeight.
baselineAtSpace := baseline.
lastIndex := lastIndex + 1.
spaceCount := spaceCount + 1.
+ lastBreakIsNotASpace := false.
destX > rightMargin ifTrue: [^self crossedX].
^false
!
Item was changed:
----- Method: DisplayScanner>>displayLine:offset:leftInRun: (in category 'scanning') -----
displayLine: textLine offset: offset leftInRun: leftInRun
"The call on the primitive (scanCharactersFrom:to:in:rightX:) will be interrupted according to an array of stop conditions passed to the scanner at which time the code to handle the stop condition is run and the call on the primitive continued until a stop condition returns true (which means the line has terminated). leftInRun is the # of characters left to scan in the current run; when 0, it is time to call setStopConditions."
| stopCondition nowLeftInRun startIndex string lastPos |
line := textLine.
morphicOffset := offset.
lineY := line top + offset y.
lineHeight := line lineHeight.
rightMargin := line rightMargin + offset x.
lastIndex := line first.
leftInRun <= 0 ifTrue: [self setStopConditions].
leftMargin := (line leftMarginForAlignment: alignment) + offset x.
destX := runX := leftMargin.
fillBlt == nil ifFalse:
["Not right"
fillBlt destX: line left destY: lineY
width: line width left height: lineHeight; copyBits].
lastIndex := line first.
leftInRun <= 0
ifTrue: [nowLeftInRun := text runLengthFor: lastIndex]
ifFalse: [nowLeftInRun := leftInRun].
destY := lineY + line baseline - font ascent.
runStopIndex := lastIndex + (nowLeftInRun - 1) min: line last.
spaceCount := 0.
string := text string.
[
"remember where this portion of the line starts"
startIndex := lastIndex.
lastPos := destX@destY.
"find the end of this portion of the line"
stopCondition := self scanCharactersFrom: lastIndex to: runStopIndex
in: string rightX: rightMargin stopConditions: stopConditions
kern: kern.
"display that portion of the line"
lastIndex >= startIndex ifTrue:[
font displayString: string on: bitBlt
from: startIndex
"XXXX: The following is an interesting bug. All stopConditions exept #endOfRun
have lastIndex past the last character displayed. #endOfRun sets it *on* the character.
If we display up until lastIndex then we will also display invisible characters like
CR and tab. This problem should be fixed in the scanner (i.e., position lastIndex
consistently) but I don't want to deal with the fallout right now so we keep the
fix minimally invasive."
to: (stopCondition == #endOfRun ifTrue:[lastIndex] ifFalse:[lastIndex-1])
at: lastPos kern: kern].
"handle the stop condition"
"see setStopConditions for stopping conditions for displaying."
+ self perform: stopCondition
- (self perform: stopCondition)
- or: [lastIndex > runStopIndex].
] whileFalse.
^ runStopIndex - lastIndex "Number of characters remaining in the current run"!
Item was added:
+ ----- Method: DisplayScanner>>isBreakableAt:in:in: (in category 'multilingual scanning') -----
+ isBreakableAt: index in: sourceString in: encodingClass
+
+ ^ false.
+ !
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 := (theText string isOctetString
- ifTrue:[CompositionScanner new]
- ifFalse:[MultiCompositionScanner 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@defaultLineHeight)
] ifFalse: [
self fixupLastLineIfCR
].
^{lines asArray. maxRightX}
!
Item was changed:
----- 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 := MultiCompositionScanner 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
- self
addNullLineWithIndex: startCharIndex
andRectangle: (theContainer topLeft extent: 0@defaultLineHeight)
] ifFalse: [
self fixupLastLineIfCR
].
^{lines asArray. maxRightX}
!
Nicolas Cellier uploaded a new version of Graphics to project The Trunk:
http://source.squeak.org/trunk/Graphics-nice.237.mcz
==================== Summary ====================
Name: Graphics-nice.237
Author: nice
Time: 29 September 2013, 4:26:28.942 pm
UUID: f73a5da8-0cca-42c7-a0b3-08472216f2cf
Ancestors: Graphics-nice.236
Copy the multi-lingual methods from MultiCharacterScanner hierarchy that were missing in CharacterScanner hierarchy.
Note that this does not include the precomposed unicode Combining which deserves a rewrite.
Also note that non-space breakable chars are handled a bit differently in CompositionScanner in order to make the minimal change that could work.
Start using the CharacterScanner hierarchy instead of the Multi one.
=============== Diff against Graphics-nice.236 ===============
Item was added:
+ ----- Method: CharacterBlockScanner>>theFirstCharCrossedX (in category 'private') -----
+ theFirstCharCrossedX
+ "Note: it is hard to explain why, but this is required to keep selection of leftmost char possible."
+ ^false!
Item was added:
+ ----- Method: CharacterScanner>>isBreakableAt:in:in: (in category 'scanner methods') -----
+ isBreakableAt: index in: sourceString in: encodingClass
+ "check with the encoding whether the character at index is a breakable character- only the JISX0208 & JapaneseEnvironment will ever return true, so only the scanJapaneseCharacters... method calls this"
+ ^ encodingClass isBreakableAt: index in: sourceString.
+ !
Item was removed:
- ----- Method: CharacterScanner>>isBreakableAtIndex: (in category 'scanner methods') -----
- isBreakableAtIndex: index
- "appears t obe unused - cf MultiCharacterScanner>isBreakableAt:in:in"
- ^ (EncodedCharSet at: ((text at: index) leadingChar + 1)) isBreakableAt: index in: text.
- !
Item was added:
+ ----- Method: CharacterScanner>>registerBreakableIndex (in category 'multilingual scanning') -----
+ registerBreakableIndex
+
+ "Record left x and character index of the line-wrappable point.
+ The default implementation here does nothing."
+
+ ^ false.
+ !
Item was changed:
----- Method: CharacterScanner>>scanJapaneseCharactersFrom:to:in:rightX:stopConditions:kern: (in category 'scanner methods') -----
scanJapaneseCharactersFrom: startIndex to: stopIndex in: sourceString rightX: rightX stopConditions: stops kern: kernDelta
| ascii encoding nextDestX startEncoding |
lastIndex := startIndex.
lastIndex > stopIndex ifTrue: [lastIndex := stopIndex. ^ stops endOfRun].
startEncoding := (sourceString at: startIndex) leadingChar.
[lastIndex <= stopIndex] whileTrue: [
encoding := (sourceString at: lastIndex) leadingChar.
encoding ~= startEncoding ifTrue: [lastIndex := lastIndex - 1. ^ stops endOfRun].
ascii := (sourceString at: lastIndex) charCode.
(encoding = 0 and: [ascii < 256 and:[(stops at: ascii + 1) notNil]])
ifTrue: [^ stops at: ascii + 1].
+ (self isBreakableAt: lastIndex in: sourceString in: (EncodedCharSet charsetAt: encoding)) ifTrue: [
+ self registerBreakableIndex.
+ ].
nextDestX := destX + (font widthOf: (sourceString at: lastIndex)).
+ nextDestX > rightX ifTrue: [self theFirstCharCrossedX ifFalse: [^ stops crossedX]].
- nextDestX > rightX ifTrue: [^ stops crossedX].
destX := nextDestX + kernDelta.
- "destX printString displayAt: 0@(lastIndex*20)."
lastIndex := lastIndex + 1.
].
lastIndex := stopIndex.
^ stops endOfRun!
Item was changed:
----- Method: CharacterScanner>>scanMultiCharactersFrom:to:in:rightX:stopConditions:kern: (in category 'scanning') -----
scanMultiCharactersFrom: startIndex to: stopIndex in: sourceString rightX: rightX stopConditions: stops kern: kernDelta
+ | ascii encoding nextDestX startEncoding floatDestX widthAndKernedWidth nextChar atEndOfRun |
- | ascii encoding nextDestX startEncoding |
lastIndex := startIndex.
lastIndex > stopIndex ifTrue: [lastIndex := stopIndex. ^ stops endOfRun].
startEncoding := (sourceString at: startIndex) leadingChar.
+ floatDestX := destX.
+ widthAndKernedWidth := Array new: 2.
+ atEndOfRun := false.
[lastIndex <= stopIndex] whileTrue: [
encoding := (sourceString at: lastIndex) leadingChar.
encoding ~= startEncoding ifTrue: [lastIndex := lastIndex - 1. ^ stops endOfRun].
ascii := (sourceString at: lastIndex) charCode.
+ (ascii < 256 and: [(stops at: ascii + 1) ~~ nil]) ifTrue: [^ stops at: ascii + 1].
+ nextChar := (lastIndex + 1 <= stopIndex)
+ ifTrue:[sourceString at: lastIndex + 1]
+ ifFalse:[
+ atEndOfRun := true.
+ "if there is a next char in sourceString, then get the kern
+ and store it in pendingKernX"
+ lastIndex + 1 <= sourceString size
+ ifTrue:[sourceString at: lastIndex + 1]
+ ifFalse:[ nil]].
+ font
+ widthAndKernedWidthOfLeft: (sourceString at: lastIndex)
+ right: nextChar
+ into: widthAndKernedWidth.
+ nextDestX := floatDestX + (widthAndKernedWidth at: 1).
+ nextDestX > rightX ifTrue: [self theFirstCharCrossedX ifFalse: [^stops crossedX]].
+ floatDestX := floatDestX + kernDelta + (widthAndKernedWidth at: 2).
+ atEndOfRun
+ ifTrue:[
+ pendingKernX := (widthAndKernedWidth at: 2) - (widthAndKernedWidth at: 1).
+ floatDestX := floatDestX - pendingKernX].
+ destX := floatDestX .
- (encoding = 0 and: [ascii < 256 and:[(stops at: ascii + 1) notNil]])
- ifTrue: [^ stops at: ascii + 1].
- nextDestX := destX + (font widthOf: (sourceString at: lastIndex)).
- nextDestX > rightX ifTrue: [^ stops crossedX].
- destX := nextDestX + kernDelta.
- "destX printString displayAt: 0@(lastIndex*20)."
lastIndex := lastIndex + 1.
].
lastIndex := stopIndex.
^ stops endOfRun!
Item was added:
+ ----- Method: CharacterScanner>>theFirstCharCrossedX (in category 'private') -----
+ theFirstCharCrossedX
+ "Tell whether the left most char crossed the right margin boundary"
+ ^destX = leftMargin!
Item was changed:
CharacterScanner subclass: #CompositionScanner
+ instanceVariableNames: 'spaceX spaceIndex lineHeight baseline lineHeightAtSpace baselineAtSpace lastBreakIsNotASpace'
- instanceVariableNames: 'spaceX spaceIndex lineHeight baseline lineHeightAtSpace baselineAtSpace'
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>>columnBreak (in category 'stop conditions') -----
columnBreak
"Answer true. Set up values for the text line interval currently being
composed."
pendingKernX := 0.
line stop: lastIndex.
spaceX := destX.
+ lastBreakIsNotASpace := false.
line paddingWidth: rightMargin - spaceX.
^true!
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).
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.
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.
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."
- space that was encountered, as recorded by the space stop condition."
pendingKernX := 0.
+
+ lastBreakIsNotASpace ifTrue:
+ ["In some languages break is possible on non space."
+ line stop: spaceIndex.
+ lineHeight := lineHeightAtSpace.
+ baseline := baselineAtSpace.
+ 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.
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].
spaceX := destX.
line paddingWidth: rightMargin - destX.
line stop: (lastIndex max: line first)].
^true!
Item was added:
+ ----- Method: CompositionScanner>>registerBreakableIndex (in category 'multilingual scanning') -----
+ registerBreakableIndex
+ "Record left x and character index of the line-wrappable point.
+ Used for wrap-around in eastern Asian languages."
+
+ spaceX := destX.
+ lineHeightAtSpace := lineHeight.
+ baselineAtSpace := baseline.
+ spaceIndex := lastIndex.
+ lastBreakIsNotASpace := true.!
Item was changed:
----- Method: CompositionScanner>>space (in category 'stop conditions') -----
space
+ "Record left x and character index of the space character just encountered.
- "Record left x and character index of the space character just encounted.
Used for wrap-around. Answer whether the character has crossed the
right edge of the composition rectangle of the paragraph."
pendingKernX := 0.
spaceX := destX.
destX := spaceX + spaceWidth.
spaceIndex := lastIndex.
lineHeightAtSpace := lineHeight.
baselineAtSpace := baseline.
lastIndex := lastIndex + 1.
spaceCount := spaceCount + 1.
+ lastBreakIsNotASpace := false.
destX > rightMargin ifTrue: [^self crossedX].
^false
!
Item was changed:
----- Method: DisplayScanner>>displayLine:offset:leftInRun: (in category 'scanning') -----
displayLine: textLine offset: offset leftInRun: leftInRun
"The call on the primitive (scanCharactersFrom:to:in:rightX:) will be interrupted according to an array of stop conditions passed to the scanner at which time the code to handle the stop condition is run and the call on the primitive continued until a stop condition returns true (which means the line has terminated). leftInRun is the # of characters left to scan in the current run; when 0, it is time to call setStopConditions."
| stopCondition nowLeftInRun startIndex string lastPos |
line := textLine.
morphicOffset := offset.
lineY := line top + offset y.
lineHeight := line lineHeight.
rightMargin := line rightMargin + offset x.
lastIndex := line first.
leftInRun <= 0 ifTrue: [self setStopConditions].
leftMargin := (line leftMarginForAlignment: alignment) + offset x.
destX := runX := leftMargin.
fillBlt == nil ifFalse:
["Not right"
fillBlt destX: line left destY: lineY
width: line width left height: lineHeight; copyBits].
lastIndex := line first.
leftInRun <= 0
ifTrue: [nowLeftInRun := text runLengthFor: lastIndex]
ifFalse: [nowLeftInRun := leftInRun].
destY := lineY + line baseline - font ascent.
runStopIndex := lastIndex + (nowLeftInRun - 1) min: line last.
spaceCount := 0.
string := text string.
[
"remember where this portion of the line starts"
startIndex := lastIndex.
lastPos := destX@destY.
"find the end of this portion of the line"
stopCondition := self scanCharactersFrom: lastIndex to: runStopIndex
in: string rightX: rightMargin stopConditions: stopConditions
kern: kern.
"display that portion of the line"
lastIndex >= startIndex ifTrue:[
font displayString: string on: bitBlt
from: startIndex
"XXXX: The following is an interesting bug. All stopConditions exept #endOfRun
have lastIndex past the last character displayed. #endOfRun sets it *on* the character.
If we display up until lastIndex then we will also display invisible characters like
CR and tab. This problem should be fixed in the scanner (i.e., position lastIndex
consistently) but I don't want to deal with the fallout right now so we keep the
fix minimally invasive."
to: (stopCondition == #endOfRun ifTrue:[lastIndex] ifFalse:[lastIndex-1])
at: lastPos kern: kern].
"handle the stop condition"
"see setStopConditions for stopping conditions for displaying."
+ self perform: stopCondition
- (self perform: stopCondition)
- or: [lastIndex > runStopIndex].
] whileFalse.
^ runStopIndex - lastIndex "Number of characters remaining in the current run"!
Item was added:
+ ----- Method: DisplayScanner>>isBreakableAt:in:in: (in category 'multilingual scanning') -----
+ isBreakableAt: index in: sourceString in: encodingClass
+
+ ^ false.
+ !
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 := (theText string isOctetString
- ifTrue:[CompositionScanner new]
- ifFalse:[MultiCompositionScanner 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@defaultLineHeight)
] ifFalse: [
self fixupLastLineIfCR
].
^{lines asArray. maxRightX}
!
Item was changed:
----- 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 := MultiCompositionScanner 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
- self
addNullLineWithIndex: startCharIndex
andRectangle: (theContainer topLeft extent: 0@defaultLineHeight)
] ifFalse: [
self fixupLastLineIfCR
].
^{lines asArray. maxRightX}
!
Nicolas Cellier uploaded a new version of Graphics to project The Trunk:
http://source.squeak.org/trunk/Graphics-nice.237.mcz
==================== Summary ====================
Name: Graphics-nice.237
Author: nice
Time: 29 September 2013, 4:26:28.942 pm
UUID: f73a5da8-0cca-42c7-a0b3-08472216f2cf
Ancestors: Graphics-nice.236
Copy the multi-lingual methods from MultiCharacterScanner hierarchy that were missing in CharacterScanner hierarchy.
Note that this does not include the precomposed unicode Combining which deserves a rewrite.
Also note that non-space breakable chars are handled a bit differently in CompositionScanner in order to make the minimal change that could work.
Start using the CharacterScanner hierarchy instead of the Multi one.
=============== Diff against Graphics-nice.236 ===============
Item was added:
+ ----- Method: CharacterBlockScanner>>theFirstCharCrossedX (in category 'private') -----
+ theFirstCharCrossedX
+ "Note: it is hard to explain why, but this is required to keep selection of leftmost char possible."
+ ^false!
Item was added:
+ ----- Method: CharacterScanner>>isBreakableAt:in:in: (in category 'scanner methods') -----
+ isBreakableAt: index in: sourceString in: encodingClass
+ "check with the encoding whether the character at index is a breakable character- only the JISX0208 & JapaneseEnvironment will ever return true, so only the scanJapaneseCharacters... method calls this"
+ ^ encodingClass isBreakableAt: index in: sourceString.
+ !
Item was removed:
- ----- Method: CharacterScanner>>isBreakableAtIndex: (in category 'scanner methods') -----
- isBreakableAtIndex: index
- "appears t obe unused - cf MultiCharacterScanner>isBreakableAt:in:in"
- ^ (EncodedCharSet at: ((text at: index) leadingChar + 1)) isBreakableAt: index in: text.
- !
Item was added:
+ ----- Method: CharacterScanner>>registerBreakableIndex (in category 'multilingual scanning') -----
+ registerBreakableIndex
+
+ "Record left x and character index of the line-wrappable point.
+ The default implementation here does nothing."
+
+ ^ false.
+ !
Item was changed:
----- Method: CharacterScanner>>scanJapaneseCharactersFrom:to:in:rightX:stopConditions:kern: (in category 'scanner methods') -----
scanJapaneseCharactersFrom: startIndex to: stopIndex in: sourceString rightX: rightX stopConditions: stops kern: kernDelta
| ascii encoding nextDestX startEncoding |
lastIndex := startIndex.
lastIndex > stopIndex ifTrue: [lastIndex := stopIndex. ^ stops endOfRun].
startEncoding := (sourceString at: startIndex) leadingChar.
[lastIndex <= stopIndex] whileTrue: [
encoding := (sourceString at: lastIndex) leadingChar.
encoding ~= startEncoding ifTrue: [lastIndex := lastIndex - 1. ^ stops endOfRun].
ascii := (sourceString at: lastIndex) charCode.
(encoding = 0 and: [ascii < 256 and:[(stops at: ascii + 1) notNil]])
ifTrue: [^ stops at: ascii + 1].
+ (self isBreakableAt: lastIndex in: sourceString in: (EncodedCharSet charsetAt: encoding)) ifTrue: [
+ self registerBreakableIndex.
+ ].
nextDestX := destX + (font widthOf: (sourceString at: lastIndex)).
+ nextDestX > rightX ifTrue: [self theFirstCharCrossedX ifFalse: [^ stops crossedX]].
- nextDestX > rightX ifTrue: [^ stops crossedX].
destX := nextDestX + kernDelta.
- "destX printString displayAt: 0@(lastIndex*20)."
lastIndex := lastIndex + 1.
].
lastIndex := stopIndex.
^ stops endOfRun!
Item was changed:
----- Method: CharacterScanner>>scanMultiCharactersFrom:to:in:rightX:stopConditions:kern: (in category 'scanning') -----
scanMultiCharactersFrom: startIndex to: stopIndex in: sourceString rightX: rightX stopConditions: stops kern: kernDelta
+ | ascii encoding nextDestX startEncoding floatDestX widthAndKernedWidth nextChar atEndOfRun |
- | ascii encoding nextDestX startEncoding |
lastIndex := startIndex.
lastIndex > stopIndex ifTrue: [lastIndex := stopIndex. ^ stops endOfRun].
startEncoding := (sourceString at: startIndex) leadingChar.
+ floatDestX := destX.
+ widthAndKernedWidth := Array new: 2.
+ atEndOfRun := false.
[lastIndex <= stopIndex] whileTrue: [
encoding := (sourceString at: lastIndex) leadingChar.
encoding ~= startEncoding ifTrue: [lastIndex := lastIndex - 1. ^ stops endOfRun].
ascii := (sourceString at: lastIndex) charCode.
+ (ascii < 256 and: [(stops at: ascii + 1) ~~ nil]) ifTrue: [^ stops at: ascii + 1].
+ nextChar := (lastIndex + 1 <= stopIndex)
+ ifTrue:[sourceString at: lastIndex + 1]
+ ifFalse:[
+ atEndOfRun := true.
+ "if there is a next char in sourceString, then get the kern
+ and store it in pendingKernX"
+ lastIndex + 1 <= sourceString size
+ ifTrue:[sourceString at: lastIndex + 1]
+ ifFalse:[ nil]].
+ font
+ widthAndKernedWidthOfLeft: (sourceString at: lastIndex)
+ right: nextChar
+ into: widthAndKernedWidth.
+ nextDestX := floatDestX + (widthAndKernedWidth at: 1).
+ nextDestX > rightX ifTrue: [self theFirstCharCrossedX ifFalse: [^stops crossedX]].
+ floatDestX := floatDestX + kernDelta + (widthAndKernedWidth at: 2).
+ atEndOfRun
+ ifTrue:[
+ pendingKernX := (widthAndKernedWidth at: 2) - (widthAndKernedWidth at: 1).
+ floatDestX := floatDestX - pendingKernX].
+ destX := floatDestX .
- (encoding = 0 and: [ascii < 256 and:[(stops at: ascii + 1) notNil]])
- ifTrue: [^ stops at: ascii + 1].
- nextDestX := destX + (font widthOf: (sourceString at: lastIndex)).
- nextDestX > rightX ifTrue: [^ stops crossedX].
- destX := nextDestX + kernDelta.
- "destX printString displayAt: 0@(lastIndex*20)."
lastIndex := lastIndex + 1.
].
lastIndex := stopIndex.
^ stops endOfRun!
Item was added:
+ ----- Method: CharacterScanner>>theFirstCharCrossedX (in category 'private') -----
+ theFirstCharCrossedX
+ "Tell whether the left most char crossed the right margin boundary"
+ ^destX = leftMargin!
Item was changed:
CharacterScanner subclass: #CompositionScanner
+ instanceVariableNames: 'spaceX spaceIndex lineHeight baseline lineHeightAtSpace baselineAtSpace lastBreakIsNotASpace'
- instanceVariableNames: 'spaceX spaceIndex lineHeight baseline lineHeightAtSpace baselineAtSpace'
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>>columnBreak (in category 'stop conditions') -----
columnBreak
"Answer true. Set up values for the text line interval currently being
composed."
pendingKernX := 0.
line stop: lastIndex.
spaceX := destX.
+ lastBreakIsNotASpace := false.
line paddingWidth: rightMargin - spaceX.
^true!
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).
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.
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.
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."
- space that was encountered, as recorded by the space stop condition."
pendingKernX := 0.
+
+ lastBreakIsNotASpace ifTrue:
+ ["In some languages break is possible on non space."
+ line stop: spaceIndex.
+ lineHeight := lineHeightAtSpace.
+ baseline := baselineAtSpace.
+ 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.
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].
spaceX := destX.
line paddingWidth: rightMargin - destX.
line stop: (lastIndex max: line first)].
^true!
Item was added:
+ ----- Method: CompositionScanner>>registerBreakableIndex (in category 'multilingual scanning') -----
+ registerBreakableIndex
+ "Record left x and character index of the line-wrappable point.
+ Used for wrap-around in eastern Asian languages."
+
+ spaceX := destX.
+ lineHeightAtSpace := lineHeight.
+ baselineAtSpace := baseline.
+ spaceIndex := lastIndex.
+ lastBreakIsNotASpace := true.!
Item was changed:
----- Method: CompositionScanner>>space (in category 'stop conditions') -----
space
+ "Record left x and character index of the space character just encountered.
- "Record left x and character index of the space character just encounted.
Used for wrap-around. Answer whether the character has crossed the
right edge of the composition rectangle of the paragraph."
pendingKernX := 0.
spaceX := destX.
destX := spaceX + spaceWidth.
spaceIndex := lastIndex.
lineHeightAtSpace := lineHeight.
baselineAtSpace := baseline.
lastIndex := lastIndex + 1.
spaceCount := spaceCount + 1.
+ lastBreakIsNotASpace := false.
destX > rightMargin ifTrue: [^self crossedX].
^false
!
Item was changed:
----- Method: DisplayScanner>>displayLine:offset:leftInRun: (in category 'scanning') -----
displayLine: textLine offset: offset leftInRun: leftInRun
"The call on the primitive (scanCharactersFrom:to:in:rightX:) will be interrupted according to an array of stop conditions passed to the scanner at which time the code to handle the stop condition is run and the call on the primitive continued until a stop condition returns true (which means the line has terminated). leftInRun is the # of characters left to scan in the current run; when 0, it is time to call setStopConditions."
| stopCondition nowLeftInRun startIndex string lastPos |
line := textLine.
morphicOffset := offset.
lineY := line top + offset y.
lineHeight := line lineHeight.
rightMargin := line rightMargin + offset x.
lastIndex := line first.
leftInRun <= 0 ifTrue: [self setStopConditions].
leftMargin := (line leftMarginForAlignment: alignment) + offset x.
destX := runX := leftMargin.
fillBlt == nil ifFalse:
["Not right"
fillBlt destX: line left destY: lineY
width: line width left height: lineHeight; copyBits].
lastIndex := line first.
leftInRun <= 0
ifTrue: [nowLeftInRun := text runLengthFor: lastIndex]
ifFalse: [nowLeftInRun := leftInRun].
destY := lineY + line baseline - font ascent.
runStopIndex := lastIndex + (nowLeftInRun - 1) min: line last.
spaceCount := 0.
string := text string.
[
"remember where this portion of the line starts"
startIndex := lastIndex.
lastPos := destX@destY.
"find the end of this portion of the line"
stopCondition := self scanCharactersFrom: lastIndex to: runStopIndex
in: string rightX: rightMargin stopConditions: stopConditions
kern: kern.
"display that portion of the line"
lastIndex >= startIndex ifTrue:[
font displayString: string on: bitBlt
from: startIndex
"XXXX: The following is an interesting bug. All stopConditions exept #endOfRun
have lastIndex past the last character displayed. #endOfRun sets it *on* the character.
If we display up until lastIndex then we will also display invisible characters like
CR and tab. This problem should be fixed in the scanner (i.e., position lastIndex
consistently) but I don't want to deal with the fallout right now so we keep the
fix minimally invasive."
to: (stopCondition == #endOfRun ifTrue:[lastIndex] ifFalse:[lastIndex-1])
at: lastPos kern: kern].
"handle the stop condition"
"see setStopConditions for stopping conditions for displaying."
+ self perform: stopCondition
- (self perform: stopCondition)
- or: [lastIndex > runStopIndex].
] whileFalse.
^ runStopIndex - lastIndex "Number of characters remaining in the current run"!
Item was added:
+ ----- Method: DisplayScanner>>isBreakableAt:in:in: (in category 'multilingual scanning') -----
+ isBreakableAt: index in: sourceString in: encodingClass
+
+ ^ false.
+ !
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 := (theText string isOctetString
- ifTrue:[CompositionScanner new]
- ifFalse:[MultiCompositionScanner 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@defaultLineHeight)
] ifFalse: [
self fixupLastLineIfCR
].
^{lines asArray. maxRightX}
!
Item was changed:
----- 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 := MultiCompositionScanner 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
- self
addNullLineWithIndex: startCharIndex
andRectangle: (theContainer topLeft extent: 0@defaultLineHeight)
] ifFalse: [
self fixupLastLineIfCR
].
^{lines asArray. maxRightX}
!
Nicolas Cellier uploaded a new version of Multilingual to project The Trunk:
http://source.squeak.org/trunk/Multilingual-nice.182.mcz
==================== Summary ====================
Name: Multilingual-nice.182
Author: nice
Time: 29 September 2013, 3:47:35.834 am
UUID: 646df1ad-f30e-455c-ac35-b17fe9933abf
Ancestors: Multilingual-nice.181
Reduce some differences with the CharacterScanner hierarchies:
This will fix a regression - centered Paragraph is not displayed corectly in MVC because inst. var. alignment was used before set in the scanner.
This was solved in 2003 in http://ftp.squeak.org/updates/6188CenteredTextFix-BG.cs
But the fix was applied on CharacterScanner branch only, while MultiCharacterScanner was already forked.
Then the MultiCharacterScanner displayLines:in:clippedBy: was connected in 2005 (from the timeStamp of the method, maybe not the inclusion in official images, you'll have to dig the updateStream).
Funnily, the selection did still use a CharacterBlockScanner, so was correctly centered (outside displayed text)
=============== Diff against Multilingual-nice.181 ===============
Item was changed:
----- Method: MultiCharacterBlockScanner>>buildCharacterBlockIn: (in category 'private') -----
buildCharacterBlockIn: para
+ "This method is used by the MVC version only."
+
+ | lineIndex runLength lineStop stopCondition |
- | lineIndex runLength lineStop done stopCondition |
"handle nullText"
(para numberOfLines = 0 or: [text size = 0])
ifTrue: [^ CharacterBlock new stringIndex: 1 "like being off end of string"
text: para text
topLeft: (para leftMarginForDisplayForLine: 1 alignment: (alignment ifNil:[textStyle alignment]))
@ para compositionRectangle top
extent: 0 @ textStyle lineGrid].
"find the line"
lineIndex := para lineIndexOfTop: characterPoint y.
destY := para topAtLineIndex: lineIndex.
line := para lines at: lineIndex.
+ lastIndex := line first.
rightMargin := para rightMarginForDisplay.
+ self setStopConditions. " also loads the font, alignment and all emphasis attributes "
(lineIndex = para numberOfLines and:
[(destY + line lineHeight) < characterPoint y])
ifTrue: ["if beyond lastLine, force search to last character"
self characterPointSetX: rightMargin]
ifFalse: [characterPoint y < (para compositionRectangle) top
ifTrue: ["force search to first line"
characterPoint := (para compositionRectangle) topLeft].
characterPoint x > rightMargin
ifTrue: [self characterPointSetX: rightMargin]].
+ destX := leftMargin := para leftMarginForDisplayForLine: lineIndex alignment: alignment.
+ nextLeftMargin:= para leftMarginForDisplayForLine: lineIndex+1 alignment: alignment.
+ runLength := text runLengthFor: line first.
+ lineStop := characterIndex "scanning for index"
+ ifNil: [ line last ]. "scanning for point"
+ runStopIndex := lastIndex + (runLength - 1) min: lineStop.
- destX := (leftMargin := para leftMarginForDisplayForLine: lineIndex alignment: (alignment ifNil:[textStyle alignment])).
- nextLeftMargin:= para leftMarginForDisplayForLine: lineIndex+1 alignment: (alignment ifNil:[textStyle alignment]).
- lastIndex := line first.
-
- self setStopConditions. "also sets font"
- runLength := (text runLengthFor: line first).
- characterIndex == nil
- ifTrue: [lineStop := line last "characterBlockAtPoint"]
- ifFalse: [lineStop := characterIndex "characterBlockForIndex"].
- (runStopIndex := lastIndex + (runLength - 1)) > lineStop
- ifTrue: [runStopIndex := lineStop].
lastCharacterExtent := 0 @ line lineHeight.
+ spaceCount := 0.
- spaceCount := 0. done := false.
self handleIndentation.
- [done]
- whileFalse:
[stopCondition := self scanCharactersFrom: lastIndex to: runStopIndex
in: text string rightX: characterPoint x
stopConditions: stopConditions kern: kern.
+ "see setStopConditions for stopping conditions for character block operations."
-
- "see setStopConditions for stopping conditions for character block operations."
self lastCharacterExtentSetX: (font widthOf: (text at: lastIndex)).
+ self perform: stopCondition] whileFalse.
+
+ ^characterIndex == nil
- (self perform: stopCondition) ifTrue:
- [characterIndex == nil
ifTrue: ["characterBlockAtPoint"
^ CharacterBlock new stringIndex: lastIndex text: text
topLeft: characterPoint + (font descentKern @ 0)
extent: lastCharacterExtent]
ifFalse: ["characterBlockForIndex"
^ CharacterBlock new stringIndex: lastIndex text: text
topLeft: characterPoint + ((font descentKern) - kern @ 0)
+ extent: lastCharacterExtent]!
- extent: lastCharacterExtent]]]!
Item was changed:
----- Method: MultiDisplayScanner>>displayLine:offset:leftInRun: (in category 'scanning') -----
displayLine: textLine offset: offset leftInRun: leftInRun
"The call on the primitive (scanCharactersFrom:to:in:rightX:) will be interrupted according to an array of stop conditions passed to the scanner at which time the code to handle the stop condition is run and the call on the primitive continued until a stop condition returns true (which means the line has terminated). leftInRun is the # of characters left to scan in the current run; when 0, it is time to call setStopConditions."
| stopCondition nowLeftInRun startIndex string lastPos baselineY |
line := textLine.
morphicOffset := offset.
lineY := line top + offset y.
lineHeight := line lineHeight.
rightMargin := line rightMargin + offset x.
lastIndex := line first.
leftInRun <= 0 ifTrue: [self setStopConditions].
leftMargin := (line leftMarginForAlignment: alignment) + offset x.
destX := runX := leftMargin.
fillBlt == nil ifFalse:
["Not right"
fillBlt destX: line left destY: lineY
width: line width left height: lineHeight; copyBits].
lastIndex := line first.
leftInRun <= 0
ifTrue: [nowLeftInRun := text runLengthFor: lastIndex]
ifFalse: [nowLeftInRun := leftInRun].
baselineY := lineY + line baseline.
destY := baselineY - font ascent.
runStopIndex := lastIndex + (nowLeftInRun - 1) min: line last.
spaceCount := 0.
string := text string.
[
+ "remember where this portion of the line starts"
startIndex := lastIndex.
lastPos := destX@destY.
+
+ "find the end of this portion of the line"
stopCondition := self scanCharactersFrom: lastIndex to: runStopIndex
in: string rightX: rightMargin stopConditions: stopConditions
kern: kern.
+
+ "display that portion of the line"
lastIndex >= startIndex ifTrue:[
bitBlt displayString: string
from: startIndex
"XXXX: The following is an interesting bug. All stopConditions exept #endOfRun
have lastIndex past the last character displayed. #endOfRun sets it *on* the character.
If we display up until lastIndex then we will also display invisible characters like
CR and tab. This problem should be fixed in the scanner (i.e., position lastIndex
consistently) but I don't want to deal with the fallout right now so we keep the
fix minimally invasive."
to: (stopCondition == #endOfRun ifTrue:[lastIndex] ifFalse:[lastIndex-1])
at: lastPos kern: kern baselineY: baselineY font: font].
(emphasisCode allMask: 4) ifTrue:[
font displayUnderlineOn: bitBlt from: lastPos x@baselineY to: destX@baselineY.
].
(emphasisCode allMask: 16) ifTrue:[
font displayStrikeoutOn: bitBlt from: lastPos x@baselineY to: destX@baselineY.
].
+
+ "handle the stop condition"
"see setStopConditions for stopping conditions for displaying."
self perform: stopCondition.
- "or: [lastIndex > runStopIndex]."
] whileFalse.
^ runStopIndex - lastIndex "Number of characters remaining in the current run"!
Item was changed:
----- Method: MultiDisplayScanner>>displayLines:in:clippedBy: (in category 'MVC-compatibility') -----
displayLines: linesInterval in: aParagraph clippedBy: visibleRectangle
"The central display routine. The call on the primitive
(scanCharactersFrom:to:in:rightX:) will be interrupted according to an
array of stop conditions passed to the scanner at which time the code to
handle the stop condition is run and the call on the primitive continued
until a stop condition returns true (which means the line has
terminated)."
| leftInRun |
"leftInRun is the # of characters left to scan in the current run;
when 0, it is time to call 'self setStopConditions'"
morphicOffset := 0@0.
leftInRun := 0.
self initializeFromParagraph: aParagraph clippedBy: visibleRectangle.
ignoreColorChanges := false.
foregroundColor := paragraphColor := aParagraph foregroundColor.
backgroundColor := aParagraph backgroundColor.
aParagraph backgroundColor isTransparent
ifTrue: [fillBlt := nil]
ifFalse: [fillBlt := bitBlt copy. "Blt to fill spaces, tabs, margins"
fillBlt sourceForm: nil; sourceOrigin: 0@0.
fillBlt fillColor: aParagraph backgroundColor].
rightMargin := aParagraph rightMarginForDisplay.
lineY := aParagraph topAtLineIndex: linesInterval first.
bitBlt destForm deferUpdatesIn: visibleRectangle while: [
linesInterval do:
[:lineIndex |
| runLength stopCondition startIndex string lastPos baselineY |
- leftMargin := aParagraph leftMarginForDisplayForLine: lineIndex alignment: (alignment ifNil:[textStyle alignment]).
- destX := (runX := leftMargin).
line := aParagraph lines at: lineIndex.
+ lastIndex := line first.
+ leftInRun <= 0
+ ifTrue: [self setStopConditions. "also sets the font, alignment and emphasisCode"
+ leftInRun := text runLengthFor: line first].
+ leftMargin := aParagraph leftMarginForDisplayForLine: lineIndex alignment: alignment.
+ destX := (runX := leftMargin).
lineHeight := line lineHeight.
fillBlt == nil ifFalse:
[fillBlt destX: visibleRectangle left destY: lineY
width: visibleRectangle width height: lineHeight; copyBits].
- lastIndex := line first.
- leftInRun <= 0
- ifTrue: [self setStopConditions. "also sets the font"
- leftInRun := text runLengthFor: line first].
baselineY := lineY + line baseline.
destY := baselineY - font ascent. "Should have happened in setFont"
runLength := leftInRun.
runStopIndex := lastIndex + (runLength - 1) min: line last.
leftInRun := leftInRun - (runStopIndex - lastIndex + 1).
spaceCount := 0.
string := text string.
self handleIndentation.
[
startIndex := lastIndex.
lastPos := destX@destY.
stopCondition := self scanCharactersFrom: lastIndex to: runStopIndex
in: string rightX: rightMargin stopConditions: stopConditions
kern: kern.
lastIndex >= startIndex ifTrue:[
font displayString: string on: bitBlt
from: startIndex to: lastIndex at: lastPos kern: kern baselineY: baselineY].
"see setStopConditions for stopping conditions for displaying."
self perform: stopCondition
] whileFalse.
fillBlt == nil ifFalse:
[fillBlt destX: destX destY: lineY width: visibleRectangle right-destX height: lineHeight; copyBits].
lineY := lineY + lineHeight]]!
Item was changed:
----- Method: MultiDisplayScanner>>initializeFromParagraph:clippedBy: (in category 'MVC-compatibility') -----
initializeFromParagraph: aParagraph clippedBy: clippingRectangle
super initializeFromParagraph: aParagraph clippedBy: clippingRectangle.
bitBlt := BitBlt asGrafPort toForm: aParagraph destinationForm.
bitBlt sourceX: 0; width: 0. "Init BitBlt so that the first call to a primitive will not fail"
+ bitBlt combinationRule:
+ ((Display depth = 1)
+ ifTrue:
+ [aParagraph rule]
+ ifFalse:
+ [Form paint]).
- bitBlt combinationRule: Form paint.
bitBlt colorMap:
(Bitmap with: 0 "Assumes 1-bit deep fonts"
with: (bitBlt destForm pixelValueFor: aParagraph foregroundColor)).
bitBlt clipRect: clippingRectangle.
!
Nicolas Cellier uploaded a new version of Multilingual to project The Trunk:
http://source.squeak.org/trunk/Multilingual-nice.182.mcz
==================== Summary ====================
Name: Multilingual-nice.182
Author: nice
Time: 29 September 2013, 3:47:35.834 am
UUID: 646df1ad-f30e-455c-ac35-b17fe9933abf
Ancestors: Multilingual-nice.181
Reduce some differences with the CharacterScanner hierarchies:
This will fix a regression - centered Paragraph is not displayed corectly in MVC because inst. var. alignment was used before set in the scanner.
This was solved in 2003 in http://ftp.squeak.org/updates/6188CenteredTextFix-BG.cs
But the fix was applied on CharacterScanner branch only, while MultiCharacterScanner was already forked.
Then the MultiCharacterScanner displayLines:in:clippedBy: was connected in 2005 (from the timeStamp of the method, maybe not the inclusion in official images, you'll have to dig the updateStream).
Funnily, the selection did still use a CharacterBlockScanner, so was correctly centered (outside displayed text)
=============== Diff against Multilingual-nice.181 ===============
Item was changed:
----- Method: MultiCharacterBlockScanner>>buildCharacterBlockIn: (in category 'private') -----
buildCharacterBlockIn: para
+ "This method is used by the MVC version only."
+
+ | lineIndex runLength lineStop stopCondition |
- | lineIndex runLength lineStop done stopCondition |
"handle nullText"
(para numberOfLines = 0 or: [text size = 0])
ifTrue: [^ CharacterBlock new stringIndex: 1 "like being off end of string"
text: para text
topLeft: (para leftMarginForDisplayForLine: 1 alignment: (alignment ifNil:[textStyle alignment]))
@ para compositionRectangle top
extent: 0 @ textStyle lineGrid].
"find the line"
lineIndex := para lineIndexOfTop: characterPoint y.
destY := para topAtLineIndex: lineIndex.
line := para lines at: lineIndex.
+ lastIndex := line first.
rightMargin := para rightMarginForDisplay.
+ self setStopConditions. " also loads the font, alignment and all emphasis attributes "
(lineIndex = para numberOfLines and:
[(destY + line lineHeight) < characterPoint y])
ifTrue: ["if beyond lastLine, force search to last character"
self characterPointSetX: rightMargin]
ifFalse: [characterPoint y < (para compositionRectangle) top
ifTrue: ["force search to first line"
characterPoint := (para compositionRectangle) topLeft].
characterPoint x > rightMargin
ifTrue: [self characterPointSetX: rightMargin]].
+ destX := leftMargin := para leftMarginForDisplayForLine: lineIndex alignment: alignment.
+ nextLeftMargin:= para leftMarginForDisplayForLine: lineIndex+1 alignment: alignment.
+ runLength := text runLengthFor: line first.
+ lineStop := characterIndex "scanning for index"
+ ifNil: [ line last ]. "scanning for point"
+ runStopIndex := lastIndex + (runLength - 1) min: lineStop.
- destX := (leftMargin := para leftMarginForDisplayForLine: lineIndex alignment: (alignment ifNil:[textStyle alignment])).
- nextLeftMargin:= para leftMarginForDisplayForLine: lineIndex+1 alignment: (alignment ifNil:[textStyle alignment]).
- lastIndex := line first.
-
- self setStopConditions. "also sets font"
- runLength := (text runLengthFor: line first).
- characterIndex == nil
- ifTrue: [lineStop := line last "characterBlockAtPoint"]
- ifFalse: [lineStop := characterIndex "characterBlockForIndex"].
- (runStopIndex := lastIndex + (runLength - 1)) > lineStop
- ifTrue: [runStopIndex := lineStop].
lastCharacterExtent := 0 @ line lineHeight.
+ spaceCount := 0.
- spaceCount := 0. done := false.
self handleIndentation.
- [done]
- whileFalse:
[stopCondition := self scanCharactersFrom: lastIndex to: runStopIndex
in: text string rightX: characterPoint x
stopConditions: stopConditions kern: kern.
+ "see setStopConditions for stopping conditions for character block operations."
-
- "see setStopConditions for stopping conditions for character block operations."
self lastCharacterExtentSetX: (font widthOf: (text at: lastIndex)).
+ self perform: stopCondition] whileFalse.
+
+ ^characterIndex == nil
- (self perform: stopCondition) ifTrue:
- [characterIndex == nil
ifTrue: ["characterBlockAtPoint"
^ CharacterBlock new stringIndex: lastIndex text: text
topLeft: characterPoint + (font descentKern @ 0)
extent: lastCharacterExtent]
ifFalse: ["characterBlockForIndex"
^ CharacterBlock new stringIndex: lastIndex text: text
topLeft: characterPoint + ((font descentKern) - kern @ 0)
+ extent: lastCharacterExtent]!
- extent: lastCharacterExtent]]]!
Item was changed:
----- Method: MultiDisplayScanner>>displayLine:offset:leftInRun: (in category 'scanning') -----
displayLine: textLine offset: offset leftInRun: leftInRun
"The call on the primitive (scanCharactersFrom:to:in:rightX:) will be interrupted according to an array of stop conditions passed to the scanner at which time the code to handle the stop condition is run and the call on the primitive continued until a stop condition returns true (which means the line has terminated). leftInRun is the # of characters left to scan in the current run; when 0, it is time to call setStopConditions."
| stopCondition nowLeftInRun startIndex string lastPos baselineY |
line := textLine.
morphicOffset := offset.
lineY := line top + offset y.
lineHeight := line lineHeight.
rightMargin := line rightMargin + offset x.
lastIndex := line first.
leftInRun <= 0 ifTrue: [self setStopConditions].
leftMargin := (line leftMarginForAlignment: alignment) + offset x.
destX := runX := leftMargin.
fillBlt == nil ifFalse:
["Not right"
fillBlt destX: line left destY: lineY
width: line width left height: lineHeight; copyBits].
lastIndex := line first.
leftInRun <= 0
ifTrue: [nowLeftInRun := text runLengthFor: lastIndex]
ifFalse: [nowLeftInRun := leftInRun].
baselineY := lineY + line baseline.
destY := baselineY - font ascent.
runStopIndex := lastIndex + (nowLeftInRun - 1) min: line last.
spaceCount := 0.
string := text string.
[
+ "remember where this portion of the line starts"
startIndex := lastIndex.
lastPos := destX@destY.
+
+ "find the end of this portion of the line"
stopCondition := self scanCharactersFrom: lastIndex to: runStopIndex
in: string rightX: rightMargin stopConditions: stopConditions
kern: kern.
+
+ "display that portion of the line"
lastIndex >= startIndex ifTrue:[
bitBlt displayString: string
from: startIndex
"XXXX: The following is an interesting bug. All stopConditions exept #endOfRun
have lastIndex past the last character displayed. #endOfRun sets it *on* the character.
If we display up until lastIndex then we will also display invisible characters like
CR and tab. This problem should be fixed in the scanner (i.e., position lastIndex
consistently) but I don't want to deal with the fallout right now so we keep the
fix minimally invasive."
to: (stopCondition == #endOfRun ifTrue:[lastIndex] ifFalse:[lastIndex-1])
at: lastPos kern: kern baselineY: baselineY font: font].
(emphasisCode allMask: 4) ifTrue:[
font displayUnderlineOn: bitBlt from: lastPos x@baselineY to: destX@baselineY.
].
(emphasisCode allMask: 16) ifTrue:[
font displayStrikeoutOn: bitBlt from: lastPos x@baselineY to: destX@baselineY.
].
+
+ "handle the stop condition"
"see setStopConditions for stopping conditions for displaying."
self perform: stopCondition.
- "or: [lastIndex > runStopIndex]."
] whileFalse.
^ runStopIndex - lastIndex "Number of characters remaining in the current run"!
Item was changed:
----- Method: MultiDisplayScanner>>displayLines:in:clippedBy: (in category 'MVC-compatibility') -----
displayLines: linesInterval in: aParagraph clippedBy: visibleRectangle
"The central display routine. The call on the primitive
(scanCharactersFrom:to:in:rightX:) will be interrupted according to an
array of stop conditions passed to the scanner at which time the code to
handle the stop condition is run and the call on the primitive continued
until a stop condition returns true (which means the line has
terminated)."
| leftInRun |
"leftInRun is the # of characters left to scan in the current run;
when 0, it is time to call 'self setStopConditions'"
morphicOffset := 0@0.
leftInRun := 0.
self initializeFromParagraph: aParagraph clippedBy: visibleRectangle.
ignoreColorChanges := false.
foregroundColor := paragraphColor := aParagraph foregroundColor.
backgroundColor := aParagraph backgroundColor.
aParagraph backgroundColor isTransparent
ifTrue: [fillBlt := nil]
ifFalse: [fillBlt := bitBlt copy. "Blt to fill spaces, tabs, margins"
fillBlt sourceForm: nil; sourceOrigin: 0@0.
fillBlt fillColor: aParagraph backgroundColor].
rightMargin := aParagraph rightMarginForDisplay.
lineY := aParagraph topAtLineIndex: linesInterval first.
bitBlt destForm deferUpdatesIn: visibleRectangle while: [
linesInterval do:
[:lineIndex |
| runLength stopCondition startIndex string lastPos baselineY |
- leftMargin := aParagraph leftMarginForDisplayForLine: lineIndex alignment: (alignment ifNil:[textStyle alignment]).
- destX := (runX := leftMargin).
line := aParagraph lines at: lineIndex.
+ lastIndex := line first.
+ leftInRun <= 0
+ ifTrue: [self setStopConditions. "also sets the font, alignment and emphasisCode"
+ leftInRun := text runLengthFor: line first].
+ leftMargin := aParagraph leftMarginForDisplayForLine: lineIndex alignment: alignment.
+ destX := (runX := leftMargin).
lineHeight := line lineHeight.
fillBlt == nil ifFalse:
[fillBlt destX: visibleRectangle left destY: lineY
width: visibleRectangle width height: lineHeight; copyBits].
- lastIndex := line first.
- leftInRun <= 0
- ifTrue: [self setStopConditions. "also sets the font"
- leftInRun := text runLengthFor: line first].
baselineY := lineY + line baseline.
destY := baselineY - font ascent. "Should have happened in setFont"
runLength := leftInRun.
runStopIndex := lastIndex + (runLength - 1) min: line last.
leftInRun := leftInRun - (runStopIndex - lastIndex + 1).
spaceCount := 0.
string := text string.
self handleIndentation.
[
startIndex := lastIndex.
lastPos := destX@destY.
stopCondition := self scanCharactersFrom: lastIndex to: runStopIndex
in: string rightX: rightMargin stopConditions: stopConditions
kern: kern.
lastIndex >= startIndex ifTrue:[
font displayString: string on: bitBlt
from: startIndex to: lastIndex at: lastPos kern: kern baselineY: baselineY].
"see setStopConditions for stopping conditions for displaying."
self perform: stopCondition
] whileFalse.
fillBlt == nil ifFalse:
[fillBlt destX: destX destY: lineY width: visibleRectangle right-destX height: lineHeight; copyBits].
lineY := lineY + lineHeight]]!
Item was changed:
----- Method: MultiDisplayScanner>>initializeFromParagraph:clippedBy: (in category 'MVC-compatibility') -----
initializeFromParagraph: aParagraph clippedBy: clippingRectangle
super initializeFromParagraph: aParagraph clippedBy: clippingRectangle.
bitBlt := BitBlt asGrafPort toForm: aParagraph destinationForm.
bitBlt sourceX: 0; width: 0. "Init BitBlt so that the first call to a primitive will not fail"
+ bitBlt combinationRule:
+ ((Display depth = 1)
+ ifTrue:
+ [aParagraph rule]
+ ifFalse:
+ [Form paint]).
- bitBlt combinationRule: Form paint.
bitBlt colorMap:
(Bitmap with: 0 "Assumes 1-bit deep fonts"
with: (bitBlt destForm pixelValueFor: aParagraph foregroundColor)).
bitBlt clipRect: clippingRectangle.
!
Nicolas Cellier uploaded a new version of Multilingual to project The Trunk:
http://source.squeak.org/trunk/Multilingual-nice.182.mcz
==================== Summary ====================
Name: Multilingual-nice.182
Author: nice
Time: 29 September 2013, 3:47:35.834 am
UUID: 646df1ad-f30e-455c-ac35-b17fe9933abf
Ancestors: Multilingual-nice.181
Reduce some differences with the CharacterScanner hierarchies:
This will fix a regression - centered Paragraph is not displayed corectly in MVC because inst. var. alignment was used before set in the scanner.
This was solved in 2003 in http://ftp.squeak.org/updates/6188CenteredTextFix-BG.cs
But the fix was applied on CharacterScanner branch only, while MultiCharacterScanner was already forked.
Then the MultiCharacterScanner displayLines:in:clippedBy: was connected in 2005 (from the timeStamp of the method, maybe not the inclusion in official images, you'll have to dig the updateStream).
Funnily, the selection did still use a CharacterBlockScanner, so was correctly centered (outside displayed text)
=============== Diff against Multilingual-nice.181 ===============
Item was changed:
----- Method: MultiCharacterBlockScanner>>buildCharacterBlockIn: (in category 'private') -----
buildCharacterBlockIn: para
+ "This method is used by the MVC version only."
+
+ | lineIndex runLength lineStop stopCondition |
- | lineIndex runLength lineStop done stopCondition |
"handle nullText"
(para numberOfLines = 0 or: [text size = 0])
ifTrue: [^ CharacterBlock new stringIndex: 1 "like being off end of string"
text: para text
topLeft: (para leftMarginForDisplayForLine: 1 alignment: (alignment ifNil:[textStyle alignment]))
@ para compositionRectangle top
extent: 0 @ textStyle lineGrid].
"find the line"
lineIndex := para lineIndexOfTop: characterPoint y.
destY := para topAtLineIndex: lineIndex.
line := para lines at: lineIndex.
+ lastIndex := line first.
rightMargin := para rightMarginForDisplay.
+ self setStopConditions. " also loads the font, alignment and all emphasis attributes "
(lineIndex = para numberOfLines and:
[(destY + line lineHeight) < characterPoint y])
ifTrue: ["if beyond lastLine, force search to last character"
self characterPointSetX: rightMargin]
ifFalse: [characterPoint y < (para compositionRectangle) top
ifTrue: ["force search to first line"
characterPoint := (para compositionRectangle) topLeft].
characterPoint x > rightMargin
ifTrue: [self characterPointSetX: rightMargin]].
+ destX := leftMargin := para leftMarginForDisplayForLine: lineIndex alignment: alignment.
+ nextLeftMargin:= para leftMarginForDisplayForLine: lineIndex+1 alignment: alignment.
+ runLength := text runLengthFor: line first.
+ lineStop := characterIndex "scanning for index"
+ ifNil: [ line last ]. "scanning for point"
+ runStopIndex := lastIndex + (runLength - 1) min: lineStop.
- destX := (leftMargin := para leftMarginForDisplayForLine: lineIndex alignment: (alignment ifNil:[textStyle alignment])).
- nextLeftMargin:= para leftMarginForDisplayForLine: lineIndex+1 alignment: (alignment ifNil:[textStyle alignment]).
- lastIndex := line first.
-
- self setStopConditions. "also sets font"
- runLength := (text runLengthFor: line first).
- characterIndex == nil
- ifTrue: [lineStop := line last "characterBlockAtPoint"]
- ifFalse: [lineStop := characterIndex "characterBlockForIndex"].
- (runStopIndex := lastIndex + (runLength - 1)) > lineStop
- ifTrue: [runStopIndex := lineStop].
lastCharacterExtent := 0 @ line lineHeight.
+ spaceCount := 0.
- spaceCount := 0. done := false.
self handleIndentation.
- [done]
- whileFalse:
[stopCondition := self scanCharactersFrom: lastIndex to: runStopIndex
in: text string rightX: characterPoint x
stopConditions: stopConditions kern: kern.
+ "see setStopConditions for stopping conditions for character block operations."
-
- "see setStopConditions for stopping conditions for character block operations."
self lastCharacterExtentSetX: (font widthOf: (text at: lastIndex)).
+ self perform: stopCondition] whileFalse.
+
+ ^characterIndex == nil
- (self perform: stopCondition) ifTrue:
- [characterIndex == nil
ifTrue: ["characterBlockAtPoint"
^ CharacterBlock new stringIndex: lastIndex text: text
topLeft: characterPoint + (font descentKern @ 0)
extent: lastCharacterExtent]
ifFalse: ["characterBlockForIndex"
^ CharacterBlock new stringIndex: lastIndex text: text
topLeft: characterPoint + ((font descentKern) - kern @ 0)
+ extent: lastCharacterExtent]!
- extent: lastCharacterExtent]]]!
Item was changed:
----- Method: MultiDisplayScanner>>displayLine:offset:leftInRun: (in category 'scanning') -----
displayLine: textLine offset: offset leftInRun: leftInRun
"The call on the primitive (scanCharactersFrom:to:in:rightX:) will be interrupted according to an array of stop conditions passed to the scanner at which time the code to handle the stop condition is run and the call on the primitive continued until a stop condition returns true (which means the line has terminated). leftInRun is the # of characters left to scan in the current run; when 0, it is time to call setStopConditions."
| stopCondition nowLeftInRun startIndex string lastPos baselineY |
line := textLine.
morphicOffset := offset.
lineY := line top + offset y.
lineHeight := line lineHeight.
rightMargin := line rightMargin + offset x.
lastIndex := line first.
leftInRun <= 0 ifTrue: [self setStopConditions].
leftMargin := (line leftMarginForAlignment: alignment) + offset x.
destX := runX := leftMargin.
fillBlt == nil ifFalse:
["Not right"
fillBlt destX: line left destY: lineY
width: line width left height: lineHeight; copyBits].
lastIndex := line first.
leftInRun <= 0
ifTrue: [nowLeftInRun := text runLengthFor: lastIndex]
ifFalse: [nowLeftInRun := leftInRun].
baselineY := lineY + line baseline.
destY := baselineY - font ascent.
runStopIndex := lastIndex + (nowLeftInRun - 1) min: line last.
spaceCount := 0.
string := text string.
[
+ "remember where this portion of the line starts"
startIndex := lastIndex.
lastPos := destX@destY.
+
+ "find the end of this portion of the line"
stopCondition := self scanCharactersFrom: lastIndex to: runStopIndex
in: string rightX: rightMargin stopConditions: stopConditions
kern: kern.
+
+ "display that portion of the line"
lastIndex >= startIndex ifTrue:[
bitBlt displayString: string
from: startIndex
"XXXX: The following is an interesting bug. All stopConditions exept #endOfRun
have lastIndex past the last character displayed. #endOfRun sets it *on* the character.
If we display up until lastIndex then we will also display invisible characters like
CR and tab. This problem should be fixed in the scanner (i.e., position lastIndex
consistently) but I don't want to deal with the fallout right now so we keep the
fix minimally invasive."
to: (stopCondition == #endOfRun ifTrue:[lastIndex] ifFalse:[lastIndex-1])
at: lastPos kern: kern baselineY: baselineY font: font].
(emphasisCode allMask: 4) ifTrue:[
font displayUnderlineOn: bitBlt from: lastPos x@baselineY to: destX@baselineY.
].
(emphasisCode allMask: 16) ifTrue:[
font displayStrikeoutOn: bitBlt from: lastPos x@baselineY to: destX@baselineY.
].
+
+ "handle the stop condition"
"see setStopConditions for stopping conditions for displaying."
self perform: stopCondition.
- "or: [lastIndex > runStopIndex]."
] whileFalse.
^ runStopIndex - lastIndex "Number of characters remaining in the current run"!
Item was changed:
----- Method: MultiDisplayScanner>>displayLines:in:clippedBy: (in category 'MVC-compatibility') -----
displayLines: linesInterval in: aParagraph clippedBy: visibleRectangle
"The central display routine. The call on the primitive
(scanCharactersFrom:to:in:rightX:) will be interrupted according to an
array of stop conditions passed to the scanner at which time the code to
handle the stop condition is run and the call on the primitive continued
until a stop condition returns true (which means the line has
terminated)."
| leftInRun |
"leftInRun is the # of characters left to scan in the current run;
when 0, it is time to call 'self setStopConditions'"
morphicOffset := 0@0.
leftInRun := 0.
self initializeFromParagraph: aParagraph clippedBy: visibleRectangle.
ignoreColorChanges := false.
foregroundColor := paragraphColor := aParagraph foregroundColor.
backgroundColor := aParagraph backgroundColor.
aParagraph backgroundColor isTransparent
ifTrue: [fillBlt := nil]
ifFalse: [fillBlt := bitBlt copy. "Blt to fill spaces, tabs, margins"
fillBlt sourceForm: nil; sourceOrigin: 0@0.
fillBlt fillColor: aParagraph backgroundColor].
rightMargin := aParagraph rightMarginForDisplay.
lineY := aParagraph topAtLineIndex: linesInterval first.
bitBlt destForm deferUpdatesIn: visibleRectangle while: [
linesInterval do:
[:lineIndex |
| runLength stopCondition startIndex string lastPos baselineY |
- leftMargin := aParagraph leftMarginForDisplayForLine: lineIndex alignment: (alignment ifNil:[textStyle alignment]).
- destX := (runX := leftMargin).
line := aParagraph lines at: lineIndex.
+ lastIndex := line first.
+ leftInRun <= 0
+ ifTrue: [self setStopConditions. "also sets the font, alignment and emphasisCode"
+ leftInRun := text runLengthFor: line first].
+ leftMargin := aParagraph leftMarginForDisplayForLine: lineIndex alignment: alignment.
+ destX := (runX := leftMargin).
lineHeight := line lineHeight.
fillBlt == nil ifFalse:
[fillBlt destX: visibleRectangle left destY: lineY
width: visibleRectangle width height: lineHeight; copyBits].
- lastIndex := line first.
- leftInRun <= 0
- ifTrue: [self setStopConditions. "also sets the font"
- leftInRun := text runLengthFor: line first].
baselineY := lineY + line baseline.
destY := baselineY - font ascent. "Should have happened in setFont"
runLength := leftInRun.
runStopIndex := lastIndex + (runLength - 1) min: line last.
leftInRun := leftInRun - (runStopIndex - lastIndex + 1).
spaceCount := 0.
string := text string.
self handleIndentation.
[
startIndex := lastIndex.
lastPos := destX@destY.
stopCondition := self scanCharactersFrom: lastIndex to: runStopIndex
in: string rightX: rightMargin stopConditions: stopConditions
kern: kern.
lastIndex >= startIndex ifTrue:[
font displayString: string on: bitBlt
from: startIndex to: lastIndex at: lastPos kern: kern baselineY: baselineY].
"see setStopConditions for stopping conditions for displaying."
self perform: stopCondition
] whileFalse.
fillBlt == nil ifFalse:
[fillBlt destX: destX destY: lineY width: visibleRectangle right-destX height: lineHeight; copyBits].
lineY := lineY + lineHeight]]!
Item was changed:
----- Method: MultiDisplayScanner>>initializeFromParagraph:clippedBy: (in category 'MVC-compatibility') -----
initializeFromParagraph: aParagraph clippedBy: clippingRectangle
super initializeFromParagraph: aParagraph clippedBy: clippingRectangle.
bitBlt := BitBlt asGrafPort toForm: aParagraph destinationForm.
bitBlt sourceX: 0; width: 0. "Init BitBlt so that the first call to a primitive will not fail"
+ bitBlt combinationRule:
+ ((Display depth = 1)
+ ifTrue:
+ [aParagraph rule]
+ ifFalse:
+ [Form paint]).
- bitBlt combinationRule: Form paint.
bitBlt colorMap:
(Bitmap with: 0 "Assumes 1-bit deep fonts"
with: (bitBlt destForm pixelValueFor: aParagraph foregroundColor)).
bitBlt clipRect: clippingRectangle.
!