Ticket #2422: text_justification.patch

File text_justification.patch, 16.0 KB (added by boyanl, 15 years ago)
  • modules/org.sophie2.base.model.text/src/main/java/org/sophie2/base/model/text/mvc/TextModelLogic.java

    ### Eclipse Workspace Patch 1.0
    #P sophie
     
    107107                                if (clickCount % 3 == 2) { 
    108108                                        Break wordBreak = Breaks.WORD_BREAK; 
    109109                                        int endPos =  wordBreak.getNextBreakPos(rawText, pos); 
    110                                         while (endPos < rawText.getEnd()  
     110                                        while (endPos <= rawText.getEnd()  
    111111                                                        && endPos > rawText.getBegin() 
    112112                                                        && CommonChar.getWordSeparators(). 
    113113                                                        contains(rawText.unitAt(endPos - 1).getChar())) { 
     
    745745                case LINE_END : 
    746746                        lineEnds = layout.getLineEnds(caret); 
    747747                        if (lineEnds != null) { 
    748                                 result = ImmTextUtils.advance(text, textBegin, lineEnds.getEnd()); 
     748                                int end = lineEnds.getEnd(); 
     749                                while (end > text.getBegin() && end < text.getEnd() && text.unitAt(end).getChar() == CommonChar.PARA_BREAK) { 
     750                                        end = ImmTextUtils.advance(text, end, -1); 
     751                                } 
     752                                         
     753                                result = ImmTextUtils.advance(text, textBegin, end); 
    749754                        } 
    750755                        break; 
    751756 
  • modules/org.sophie2.base.model.text/src/main/java/org/sophie2/base/model/text/model/TextUtils.java

     
    2626         */ 
    2727        public static int findPrevChar(ImmText text, int startPos, List<Character> chars) { 
    2828                int pos = (startPos != -1) ? startPos : text.getBegin(); 
     29                if (pos != text.getBegin() && pos == text.getEnd()) { 
     30                        --pos; 
     31                } 
    2932 
    3033                while (pos > text.getBegin()) { 
    3134                        if (chars.contains(text.unitAt(pos).getChar())) { 
     
    5659         */ 
    5760        public static int findNextChar(ImmText text, int startIndex, List<Character> chars) { 
    5861 
    59                 assert startIndex >= 0 && startIndex < text.getEnd() : startIndex; 
     62                assert startIndex >= 0 && startIndex <= text.getEnd() : startIndex; 
     63                if (startIndex == text.getEnd ()) 
     64                        return startIndex; 
    6065                int index = startIndex + 1; 
    6166                 
    6267                while (index < text.getEnd()) { 
  • modules/org.sophie2.base.model.text/src/main/java/org/sophie2/base/model/text/layout/LayoutUtils.java

     
    5858 
    5959                        List<Character> lineBreaks = CommonChar.getEffectiveBreaks(CommonChar.LINE_BREAK); 
    6060                        int pos = TextUtils.findNextChar(this.areaText, begin, lineBreaks); 
    61  
     61                         
    6262                        if (pos == -1 || this.areaText.getEnd() == 0) {  
    6363                                return null; 
    6464                        } 
     
    7070                                        pos = nextPos; 
    7171                                } 
    7272                        } 
     73                         
     74                        //checks for trailing para break and adds it (if any) 
     75                        int paraBreak = nextPos; 
     76                        if (paraBreak < this.areaText.getEnd() &&  
     77                                        this.areaText.unitAt(paraBreak).getChar() == CommonChar.PARA_BREAK ) 
     78                                ++paraBreak; 
    7379 
    7480                        assert ImmTextUtils.isIndexInText(pos, this.areaText); 
    7581                        ImmText res =  
    7682                                this.areaText.subText(new ImmTextInterval(begin,  
    77                                                 Math.min(nextPos, this.areaText.getEnd()))); 
     83                                                Math.min(paraBreak, this.areaText.getEnd()))); 
    7884                        this.areaText =  
    79                                 this.areaText.subText(new ImmTextInterval(pos, end)); 
     85                                this.areaText.subText(new ImmTextInterval(paraBreak, end)); 
    8086 
    8187                        // Empty texts should not be returned. 
    8288                        if (res.getEnd() == 0) { 
  • modules/org.sophie2.base.model.text/src/main/java/org/sophie2/base/model/text/layout/LineBreakUtil.java

     
    55 
    66import org.sophie2.base.model.text.elements.Break; 
    77import org.sophie2.base.model.text.elements.Breaks; 
     8import org.sophie2.base.model.text.elements.CommonChar; 
    89import org.sophie2.base.model.text.model.ImmText; 
    910import org.sophie2.base.model.text.model.TextRun; 
    1011 
     
    124125                 *                      The text to take a word from. 
    125126                 * @param startIndex 
    126127                 *                      The index from which to search the word end. 
     128                 * @param whitespaceBreaks  
     129                 *                      An list of indices containing all the positions of characters who come after consecutive whitespaces 
    127130                 * @return 
    128131                 *                      A {@link Word} with starting index - startIndex and ending index - the first  
    129132                 *                      non-word-breaking character index after the first sequence of white spaces. 
    130133                 */ 
    131                 static Word get(ImmText text, int startIndex) { 
     134                static Word get(ImmText text, int startIndex, List<Integer> whitespaceBreaks) { 
    132135 
    133136                        int requiredEnd = getWordEnd(text, startIndex); 
    134137 
     
    138141 
    139142                        assert requiredEnd <= text.getEnd(); 
    140143 
     144                        if (whitespaceBreaks != null) { 
     145                                /* 
     146                                 * Construct the list of whitespace positions. The actual values in the list are the first characters *after* 
     147                                 * whitespaces. Also if the word has trailing whitespaces, the position after the word's end is inserted in the list. 
     148                                 */ 
     149                                int ind = start; 
     150                                while (ind < requiredEnd) { 
     151                                        char c = text.unitAt(ind).getChar(); 
     152                                        if (c == CommonChar.SPACE || c == CommonChar.TAB) { 
     153                                                 
     154                                                while (ind < requiredEnd &&  
     155                                                                ((c = text.unitAt(ind).getChar()) == CommonChar.SPACE || 
     156                                                                c == CommonChar.TAB || 
     157                                                                c == CommonChar.LINE_BREAK)) { 
     158                                                        ++ind; 
     159                                                } 
     160                                                         
     161                                                whitespaceBreaks.add(ind); 
     162                                        } 
     163                                        else 
     164                                                ++ind; 
     165                                } 
     166                                 
     167                                /* 
     168                                 * Construct each run between whitespace positions, starting from start 
     169                                 */ 
     170                                for (int position : whitespaceBreaks) { 
     171                                        while (start < position  
     172                                                        && (run = TextRun.create(text, start, position)) != null) { 
     173                                                runs.add (run); 
     174                                                start += run.getRunLength(); 
     175                                        } 
     176                                        start = position; 
     177                                } 
     178                        } 
     179                         
     180                        //Construct the final run (from the last whitespace position till the end of the word) 
    141181                        while (start < requiredEnd  
    142182                                        && (run = TextRun.create(text, start, requiredEnd)) != null) { 
    143183                                runs.add(run); 
  • modules/org.sophie2.base.model.text/src/main/java/org/sophie2/base/model/text/layout/HotAreaLayout.java

     
    143143                                List<HorizontalInterval> intervals =  
    144144                                        LayoutUtils.splitToIntervals(newArea, baseY, lineHeight); 
    145145                                 
     146                                char lastChar = textLine.unitAt(textLine.getEnd() - 1).getChar(); 
     147                                boolean newPara = lastChar == CommonChar.PARA_BREAK, 
     148                                                endOfText = lastChar == CommonChar.DOC_BREAK; 
     149                                 
    146150                                HotLineLayout newLineLayout =  
    147                                         HotLineLayout.create(textLine, intervals, lineHeight, baseY, baselineOffset); 
    148                                 lineList.add(newLineLayout); 
     151                                        HotLineLayout.create(textLine, intervals, lineHeight, baseY, baselineOffset, newPara || endOfText); 
     152                                 
    149153                                int lineLength = newLineLayout.getConsumedLength(); 
    150154                                consumedLength += lineLength; 
     155                                 
     156                                /* 
     157                                 * We don't know whether the text line will be fully consumed in the created line layout 
     158                                 * So after we've created it, we check whether it is consumed, and if it's not and it has a para/doc break, 
     159                                 * we recreate the layout 
     160                                 */ 
     161                                if ((newPara || endOfText) && lineLength < textLine.getEnd()) 
     162                                        newLineLayout = HotLineLayout.create(textLine, intervals, lineHeight, baseY, baselineOffset, false); 
     163                                 
     164                                lineList.add(newLineLayout); 
    151165 
    152166                                baseY += lineHeight; 
    153167 
     
    157171                                        break; 
    158172                                } 
    159173 
    160                                 boolean newPara =  
    161                                         (textLine.unitAt(textLine.getEnd() - 1).getChar() == CommonChar.PARA_BREAK); 
    162174                                 
     175                                 
     176                                 
    163177                                textLine = TextUtils.getSuffix(textLine, lineLength); 
    164178                                if (textLine.getEnd() == 0 && newPara) { 
    165179                                        baseY += belowIndent; 
  • modules/org.sophie2.base.model.text/src/main/java/org/sophie2/base/model/text/elements/Breaks.java

     
    3636                                } 
    3737                                tmpPos += advanceStep; 
    3838                        } 
    39                         return position; 
     39                        return tmpPos; 
    4040                } 
    4141        }; 
    4242 
  • modules/org.sophie2.base.model.text/src/main/java/org/sophie2/base/model/text/layout/HotSegmentLayout.java

     
    99import java.awt.geom.Line2D; 
    1010import java.awt.geom.Rectangle2D; 
    1111import java.util.ArrayList; 
     12import java.util.Arrays; 
     13import java.util.List; 
    1214 
    1315import org.sophie2.base.commons.util.position.ImmPoint; 
    1416import org.sophie2.base.model.text.elements.CommonAttr; 
     
    3335         * Layout with no text. Used to fill spaces that should stay empty. 
    3436         */ 
    3537        static final HotSegmentLayout EMPTY =  
    36                 new HotSegmentLayout(new ArrayList<TextRun>(), new ArrayList<DoublePoint>(), 0); 
     38                new HotSegmentLayout(new ArrayList<TextRun>(), new ArrayList<DoublePoint>(), new Integer[1], 0, 0); 
    3739 
    3840        private final ArrayList<TextRun> textRuns; 
    3941        private final ArrayList<DoublePoint> locations; 
     42        private final Integer[] wordPos; 
    4043        private final double xOffset; 
     44        private final double textWidth; 
    4145 
    4246        private HotSegmentLayout( 
    43                         ArrayList<TextRun> runs, ArrayList<DoublePoint> locations, double xOffset) { 
     47                        ArrayList<TextRun> runs, ArrayList<DoublePoint> locations, Integer[] wordPos, double xOffset, double textWidth) { 
    4448                 
    4549                this.textRuns = runs; 
    4650                this.locations = locations; 
    4751                this.xOffset = xOffset; 
     52                this.textWidth = textWidth; 
     53                this.wordPos = wordPos; 
    4854        } 
    4955 
    5056        /** 
     
    5864         *                      The {@link HorizontalInterval} available for this segment. 
    5965         * @param baselineOffset 
    6066         *                      The baseline Y position of the text. 
     67         * @param lastInParagraph  
     68         *                      Shows whether this segment is part of the last line of a paragraph. 
    6169         * @return 
    6270         *                      The created layout. 
    6371         */ 
    6472        public static HotSegmentLayout create( 
    65                         int startPos, ImmText text, HorizontalInterval interval, float baselineOffset) { 
     73                        int startPos, ImmText text, HorizontalInterval interval, float baselineOffset, boolean lastInParagraph) { 
    6674 
    6775                final double totalWidth = interval.getWidth(); 
    6876                double remainingWidth = totalWidth; 
    6977                ArrayList<TextRun> runs = new ArrayList<TextRun>(); 
    7078                ArrayList<DoublePoint> locations = new ArrayList<DoublePoint>(); 
     79                 
     80                //contains the start indices of whitespace-delimited words (except the first one)  
     81                ArrayList<Integer> wordPos = new ArrayList<Integer> (); 
    7182 
    7283                int position = startPos; 
    73                 Word word; 
     84                Word word = null; 
    7485 
    7586                // Add as many words as possible. 
     87                ArrayList<Integer> curWordPos = new ArrayList<Integer> (); 
    7688                while (position < text.getEnd() &&  
    77                                 (word = Word.get(text, position)) != null && 
     89                                (word = Word.get(text, position, curWordPos)) != null && 
    7890                                word.willFit(totalWidth, remainingWidth) && remainingWidth > 0) { 
    7991 
    8092                        for (TextRun run : word.getRuns()) { 
     
    98110                        } 
    99111 
    100112                        position += word.getSize(); 
     113                        wordPos.addAll(curWordPos); 
     114                        curWordPos.clear(); 
    101115                } 
    102  
     116                 
     117                /* 
     118                 * Position contains the first position of a word we were unable to add - either the end of the text (i.e. we added everything), or 
     119                 * some index before it. If we have it in wordPos (i.e. it was the first positions after whitespaces), we have to remove it. 
     120                 */ 
     121                if (!wordPos.isEmpty () && wordPos.get(wordPos.size() - 1) == position) { 
     122                        wordPos.remove(wordPos.size() - 1); 
     123                } 
     124         
    103125                // If no words are added, add as many chars as possible. 
    104126                if (runs.size() == 0) { 
    105127                        SophieLog.debug("Could not layout a single word! Will split it to chars!"); 
     
    113135                                TextRun run = character.getRun(); 
    114136 
    115137                                remainingWidth = addRun(run, baselineOffset,  
    116                                                 totalWidth, remainingWidth, runs, locations); 
     138                                                                        totalWidth, remainingWidth, runs, locations); 
    117139 
    118140                                if (remainingWidth == 0) { 
    119141                                        break; 
     
    143165 
    144166                TextAlign align = runs.get(0).getAttrValue(CommonAttr.PARA_ALIGNMENT); 
    145167 
     168                Integer[] arr = new Integer[wordPos.size()]; 
     169                for (int i = 0; i < wordPos.size(); ++i) 
     170                        arr[i] = wordPos.get(i) - startPos; 
     171                 
    146172                HotSegmentLayout result =  
    147                         new HotSegmentLayout(runs, locations, getAlignOffset(align, remainingWidth)); 
     173                        new HotSegmentLayout(runs, locations, arr, getAlignOffset(align, remainingWidth), totalWidth - remainingWidth); 
    148174 
    149                 if (TextAlign.JUSTIFIED.equals(align)) { 
     175                if (TextAlign.JUSTIFIED.equals(align) && !lastInParagraph) { 
    150176                        return result.getJustifiedLayout(totalWidth); 
    151177                } 
    152178 
     
    391417        } 
    392418 
    393419        private HotSegmentLayout getJustifiedLayout(double totalWidth) { 
    394                 // TODO don't know how to do this.. --kyli 
    395                 return this; 
     420                HotSegmentLayout result; 
     421                 
     422                /* 
     423                 * Calculate new locations for the runs, based on how much space  
     424                 * we have remaining 
     425                 */              
     426                 
     427                ArrayList<DoublePoint> newLocations = new ArrayList<DoublePoint> (this.locations.size()); 
     428                int pos = 0, placed = 0; 
     429                 
     430                double remWidth = totalWidth - this.textWidth, averageW = (this.wordPos.length == 0 ? 0 : remWidth / (this.wordPos.length)); 
     431                //do stuff with locations 
     432                for (int i = 0; i < this.textRuns.size(); ++i) { 
     433                         
     434                        boolean found = (pos != 0 && Arrays.binarySearch (this.wordPos, pos) >= 0); 
     435                        placed += (found ? 1 : 0); 
     436                                         
     437                        DoublePoint element = this.locations.get(i); 
     438                        DoublePoint newElem = new DoublePoint (element.getX() + placed*averageW, element.getY()); 
     439                        newLocations.add(newElem);                               
     440                         
     441                        pos += this.textRuns.get(i).getRunLength(); 
     442                } 
     443                result = new HotSegmentLayout (this.textRuns, newLocations, this.wordPos, this.xOffset, this.textWidth); 
     444                 
     445                return result; 
    396446        } 
    397447} 
  • modules/org.sophie2.base.model.text/src/main/java/org/sophie2/base/model/text/layout/HotLineLayout.java

     
    6060         *                      The Y position of the line, related to its parent area. 
    6161         * @param baselineOffset 
    6262         *                      The position of the text baseline - it will be aligned to it.  
     63         * @param lastLineInPara  
     64         *                      A value determining whether the lineText we're constructing from is last in its paragraph. 
    6365         * @return 
    6466         *                      The new {@link HotAreaLayout}. 
    6567         */ 
    6668        public static HotLineLayout create(ImmText lineText, List<HorizontalInterval> intervals,  
    67                         float lineHeight, float baseY, float baselineOffset) { 
     69                        float lineHeight, float baseY, float baselineOffset, boolean lastLineInPara) { 
    6870 
    6971                List<HotSegmentLayout> segmentList = new ArrayList<HotSegmentLayout>(); 
    7072                ImmText text = lineText; 
     
    7476                for (HorizontalInterval interval : intervals) { 
    7577 
    7678                        HotSegmentLayout newSegmentLayout =  
    77                                 HotSegmentLayout.create(position, text, interval, baselineOffset); 
     79                                HotSegmentLayout.create(position, text, interval, baselineOffset, lastLineInPara); 
    7880 
    7981                        segmentList.add(newSegmentLayout);  
    8082