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
107 107 if (clickCount % 3 == 2) { 108 108 Break wordBreak = Breaks.WORD_BREAK; 109 109 int endPos = wordBreak.getNextBreakPos(rawText, pos); 110 while (endPos < rawText.getEnd()110 while (endPos <= rawText.getEnd() 111 111 && endPos > rawText.getBegin() 112 112 && CommonChar.getWordSeparators(). 113 113 contains(rawText.unitAt(endPos - 1).getChar())) { … … 745 745 case LINE_END : 746 746 lineEnds = layout.getLineEnds(caret); 747 747 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); 749 754 } 750 755 break; 751 756 -
modules/org.sophie2.base.model.text/src/main/java/org/sophie2/base/model/text/model/TextUtils.java
26 26 */ 27 27 public static int findPrevChar(ImmText text, int startPos, List<Character> chars) { 28 28 int pos = (startPos != -1) ? startPos : text.getBegin(); 29 if (pos != text.getBegin() && pos == text.getEnd()) { 30 --pos; 31 } 29 32 30 33 while (pos > text.getBegin()) { 31 34 if (chars.contains(text.unitAt(pos).getChar())) { … … 56 59 */ 57 60 public static int findNextChar(ImmText text, int startIndex, List<Character> chars) { 58 61 59 assert startIndex >= 0 && startIndex < text.getEnd() : startIndex; 62 assert startIndex >= 0 && startIndex <= text.getEnd() : startIndex; 63 if (startIndex == text.getEnd ()) 64 return startIndex; 60 65 int index = startIndex + 1; 61 66 62 67 while (index < text.getEnd()) { -
modules/org.sophie2.base.model.text/src/main/java/org/sophie2/base/model/text/layout/LayoutUtils.java
58 58 59 59 List<Character> lineBreaks = CommonChar.getEffectiveBreaks(CommonChar.LINE_BREAK); 60 60 int pos = TextUtils.findNextChar(this.areaText, begin, lineBreaks); 61 61 62 62 if (pos == -1 || this.areaText.getEnd() == 0) { 63 63 return null; 64 64 } … … 70 70 pos = nextPos; 71 71 } 72 72 } 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; 73 79 74 80 assert ImmTextUtils.isIndexInText(pos, this.areaText); 75 81 ImmText res = 76 82 this.areaText.subText(new ImmTextInterval(begin, 77 Math.min( nextPos, this.areaText.getEnd())));83 Math.min(paraBreak, this.areaText.getEnd()))); 78 84 this.areaText = 79 this.areaText.subText(new ImmTextInterval(p os, end));85 this.areaText.subText(new ImmTextInterval(paraBreak, end)); 80 86 81 87 // Empty texts should not be returned. 82 88 if (res.getEnd() == 0) { -
modules/org.sophie2.base.model.text/src/main/java/org/sophie2/base/model/text/layout/LineBreakUtil.java
5 5 6 6 import org.sophie2.base.model.text.elements.Break; 7 7 import org.sophie2.base.model.text.elements.Breaks; 8 import org.sophie2.base.model.text.elements.CommonChar; 8 9 import org.sophie2.base.model.text.model.ImmText; 9 10 import org.sophie2.base.model.text.model.TextRun; 10 11 … … 124 125 * The text to take a word from. 125 126 * @param startIndex 126 127 * 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 127 130 * @return 128 131 * A {@link Word} with starting index - startIndex and ending index - the first 129 132 * non-word-breaking character index after the first sequence of white spaces. 130 133 */ 131 static Word get(ImmText text, int startIndex ) {134 static Word get(ImmText text, int startIndex, List<Integer> whitespaceBreaks) { 132 135 133 136 int requiredEnd = getWordEnd(text, startIndex); 134 137 … … 138 141 139 142 assert requiredEnd <= text.getEnd(); 140 143 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) 141 181 while (start < requiredEnd 142 182 && (run = TextRun.create(text, start, requiredEnd)) != null) { 143 183 runs.add(run); -
modules/org.sophie2.base.model.text/src/main/java/org/sophie2/base/model/text/layout/HotAreaLayout.java
143 143 List<HorizontalInterval> intervals = 144 144 LayoutUtils.splitToIntervals(newArea, baseY, lineHeight); 145 145 146 char lastChar = textLine.unitAt(textLine.getEnd() - 1).getChar(); 147 boolean newPara = lastChar == CommonChar.PARA_BREAK, 148 endOfText = lastChar == CommonChar.DOC_BREAK; 149 146 150 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 149 153 int lineLength = newLineLayout.getConsumedLength(); 150 154 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); 151 165 152 166 baseY += lineHeight; 153 167 … … 157 171 break; 158 172 } 159 173 160 boolean newPara =161 (textLine.unitAt(textLine.getEnd() - 1).getChar() == CommonChar.PARA_BREAK);162 174 175 176 163 177 textLine = TextUtils.getSuffix(textLine, lineLength); 164 178 if (textLine.getEnd() == 0 && newPara) { 165 179 baseY += belowIndent; -
modules/org.sophie2.base.model.text/src/main/java/org/sophie2/base/model/text/elements/Breaks.java
36 36 } 37 37 tmpPos += advanceStep; 38 38 } 39 return position;39 return tmpPos; 40 40 } 41 41 }; 42 42 -
modules/org.sophie2.base.model.text/src/main/java/org/sophie2/base/model/text/layout/HotSegmentLayout.java
9 9 import java.awt.geom.Line2D; 10 10 import java.awt.geom.Rectangle2D; 11 11 import java.util.ArrayList; 12 import java.util.Arrays; 13 import java.util.List; 12 14 13 15 import org.sophie2.base.commons.util.position.ImmPoint; 14 16 import org.sophie2.base.model.text.elements.CommonAttr; … … 33 35 * Layout with no text. Used to fill spaces that should stay empty. 34 36 */ 35 37 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); 37 39 38 40 private final ArrayList<TextRun> textRuns; 39 41 private final ArrayList<DoublePoint> locations; 42 private final Integer[] wordPos; 40 43 private final double xOffset; 44 private final double textWidth; 41 45 42 46 private HotSegmentLayout( 43 ArrayList<TextRun> runs, ArrayList<DoublePoint> locations, double xOffset) {47 ArrayList<TextRun> runs, ArrayList<DoublePoint> locations, Integer[] wordPos, double xOffset, double textWidth) { 44 48 45 49 this.textRuns = runs; 46 50 this.locations = locations; 47 51 this.xOffset = xOffset; 52 this.textWidth = textWidth; 53 this.wordPos = wordPos; 48 54 } 49 55 50 56 /** … … 58 64 * The {@link HorizontalInterval} available for this segment. 59 65 * @param baselineOffset 60 66 * The baseline Y position of the text. 67 * @param lastInParagraph 68 * Shows whether this segment is part of the last line of a paragraph. 61 69 * @return 62 70 * The created layout. 63 71 */ 64 72 public static HotSegmentLayout create( 65 int startPos, ImmText text, HorizontalInterval interval, float baselineOffset ) {73 int startPos, ImmText text, HorizontalInterval interval, float baselineOffset, boolean lastInParagraph) { 66 74 67 75 final double totalWidth = interval.getWidth(); 68 76 double remainingWidth = totalWidth; 69 77 ArrayList<TextRun> runs = new ArrayList<TextRun>(); 70 78 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> (); 71 82 72 83 int position = startPos; 73 Word word ;84 Word word = null; 74 85 75 86 // Add as many words as possible. 87 ArrayList<Integer> curWordPos = new ArrayList<Integer> (); 76 88 while (position < text.getEnd() && 77 (word = Word.get(text, position )) != null &&89 (word = Word.get(text, position, curWordPos)) != null && 78 90 word.willFit(totalWidth, remainingWidth) && remainingWidth > 0) { 79 91 80 92 for (TextRun run : word.getRuns()) { … … 98 110 } 99 111 100 112 position += word.getSize(); 113 wordPos.addAll(curWordPos); 114 curWordPos.clear(); 101 115 } 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 103 125 // If no words are added, add as many chars as possible. 104 126 if (runs.size() == 0) { 105 127 SophieLog.debug("Could not layout a single word! Will split it to chars!"); … … 113 135 TextRun run = character.getRun(); 114 136 115 137 remainingWidth = addRun(run, baselineOffset, 116 totalWidth, remainingWidth, runs, locations);138 totalWidth, remainingWidth, runs, locations); 117 139 118 140 if (remainingWidth == 0) { 119 141 break; … … 143 165 144 166 TextAlign align = runs.get(0).getAttrValue(CommonAttr.PARA_ALIGNMENT); 145 167 168 Integer[] arr = new Integer[wordPos.size()]; 169 for (int i = 0; i < wordPos.size(); ++i) 170 arr[i] = wordPos.get(i) - startPos; 171 146 172 HotSegmentLayout result = 147 new HotSegmentLayout(runs, locations, getAlignOffset(align, remainingWidth));173 new HotSegmentLayout(runs, locations, arr, getAlignOffset(align, remainingWidth), totalWidth - remainingWidth); 148 174 149 if (TextAlign.JUSTIFIED.equals(align) ) {175 if (TextAlign.JUSTIFIED.equals(align) && !lastInParagraph) { 150 176 return result.getJustifiedLayout(totalWidth); 151 177 } 152 178 … … 391 417 } 392 418 393 419 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; 396 446 } 397 447 } -
modules/org.sophie2.base.model.text/src/main/java/org/sophie2/base/model/text/layout/HotLineLayout.java
60 60 * The Y position of the line, related to its parent area. 61 61 * @param baselineOffset 62 62 * 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. 63 65 * @return 64 66 * The new {@link HotAreaLayout}. 65 67 */ 66 68 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) { 68 70 69 71 List<HotSegmentLayout> segmentList = new ArrayList<HotSegmentLayout>(); 70 72 ImmText text = lineText; … … 74 76 for (HorizontalInterval interval : intervals) { 75 77 76 78 HotSegmentLayout newSegmentLayout = 77 HotSegmentLayout.create(position, text, interval, baselineOffset );79 HotSegmentLayout.create(position, text, interval, baselineOffset, lastLineInPara); 78 80 79 81 segmentList.add(newSegmentLayout); 80 82