[squeak-dev] Features of text composition with different alignments and CharacterBlockScanner

Nicolas Cellier nicolas.cellier.aka.nice at gmail.com
Sun Sep 29 20:04:54 UTC 2013

Currently, the scanner family is wrapping the composition/display on next
line when line is too long.
With a single space, the feature is quite simple, a space is then
conceptually equal to a cr (and a line feed if you are stuck to the
So for example in rightFlush alignment:
- align character before space with right margin
- let character after space lay on next line
- do not materialize any spacing in text selection
selecting before the space let the cursor at upper line, right margin.
selecting after the space let the cursor at lower line, left of next

But what if there are two spaces?
Should spacing be materialized before right margin or left of next line?
IMHO, when rightFlush, spacing should be materialized left of next line, by
symmetry with leftFlush (in which case they are materialized right of
current line).

And what about Centered? And Justified?

Currently CharacterBlockScanner and rightFlush alignment don't play well
Even leftFlush doesn't correctly update the cursor position (and I checked
that this was already broken before I touched anything).

It's not really amazing, looking at the bunch of states that come onto the
- spaceCount for adjusting the padding in Justified case
- spaceX for determining the (left) position of last space (or other
breakable char)
- lastIndex the index of next character to be processed in the text
- runStopIndex the last index in the text from current position (?) before
a change of run (emphasis)
- destX the (left) position for next character to go
- kern & pendingKernX for adjusting spacing of next character (if in same
plus all the ones specific for CharacterBlockScanner:
- lastCharacter the last character that was processed
- lastCharacterExtent  its extent
- lastSpaceOrTabExtent the extent of last spce or tab (why?)
- specialWidth if it wasn't a character but an embedded morph

And the number of conditions scattered in the stop conditions:

    ((characterIndex ~= nil
        and: [characterIndex > text size])
            or: [(line last = text size)
                and: [(destY + line lineHeight) < characterPoint y]])

    characterIndex == nil ifFalse: [
        characterIndex > text size ifTrue: [

    characterPoint x <= (destX + (lastCharacterExtent x // 2))
        ifTrue:    [

    lastIndex >= line last
        ifTrue:    [

    (((characterIndex ~~ nil and:
        [runStopIndex < characterIndex and: [runStopIndex < text size]])
            or:    [characterIndex == nil and: [lastIndex < line last]])
or: [
                ((lastIndex < line last)
                and: [((text at: lastIndex) leadingChar ~= (text at:
lastIndex+1) leadingChar)
                    and: [lastIndex ~= characterIndex]])])
        ifTrue:    [

    ((lastCharacter = Space and: [alignment = Justified])
        or: [lastCharacter = Tab and: [lastSpaceOrTabExtent notNil]])
        ifTrue: [

    runStopIndex = text size
        ifTrue:    [

    (destX + lastSpaceOrTabExtent x)  >= characterPoint x
        ifTrue: [

    (alignment = Justified and: [self leadingTab not])
        ifTrue:        "imbedded tabs in justified text are weird"

    currentX >= characterPoint x

Plus the ones before and after scanning

    (text isEmpty or: [(characterPoint y < destY or: [characterPoint x <
                or: [characterIndex notNil and: [characterIndex < line
        ifTrue:    [

        ifNil: [
            (stopCondition ~~ #cr and: [ lastIndex = line last
                and: [ aPoint x > ((characterPoint x) +
(lastCharacterExtent x / 2)) ]])
                    ifTrue: [

Of course, a MVC Paragraph does not behave exactly the same.

I cannot really reverse engineer the state machine... It's not our best
I can conjecture that:
- characterIndex ~~ nil tests are probably related to the fact that moving
cursor position with mouse or with arrow keys does not always result in
consistent behavior (the cursor disappear in one case, remain visible in
the other)
- testing once for (destX + lastSpaceOrTabExtent x) and the other for
(destX + (lastCharacterExtent x // 2)) sounds weird

If you have any advice, reminiscence of bug report, useful comments to
explain each and every logic, or anything related in this area, that might
be helpful.

-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.squeakfoundation.org/pipermail/squeak-dev/attachments/20130929/7cee5794/attachment.htm

More information about the Squeak-dev mailing list