懒羊羊
2023-08-30 1ac2bc1590406d9babec036e154d8d08f34a6aa1
提交 | 用户 | 时间
1ac2bc 1 /**
2  * Word wrapping
3  * 
4  * @author (Javascript) Dmitry Farafonov
5  */
6
7         var AttributedStringIterator = function(text){
8                 //this.text = this.rtrim(this.ltrim(text));
9                 text = text.replace(/(\s)+/, " ");
10                 this.text = this.rtrim(text);
11                 /*
12                 if (beginIndex < 0 || beginIndex > endIndex || endIndex > length()) {
13                     throw new IllegalArgumentException("Invalid substring range");
14                 }
15                 */
16                 this.beginIndex = 0;
17                 this.endIndex = this.text.length;
18                 this.currentIndex = this.beginIndex;
19                 
20                 //console.group("[AttributedStringIterator]");
21                 var i = 0;
22                 var string = this.text;
23                 var fullPos = 0;
24                 
25                 //console.log("string: \"" + string + "\", length: " + string.length);
26                 this.startWordOffsets = [];
27                 this.startWordOffsets.push(fullPos);
28                 
29                 // TODO: remove i 1000
30                 while (i<1000) {
31                     var pos = string.search(/[ \t\n\f-\.\,]/);
32                     if (pos == -1)
33                         break;
34                     
35                     // whitespace start
36                     fullPos += pos;
37                     string = string.substr(pos);
38                     ////console.log("fullPos: " + fullPos + ", pos: " + pos +  ", string: ", string);
39                     
40                     // remove whitespaces
41                     var pos = string.search(/[^ \t\n\f-\.\,]/);
42                     if (pos == -1)
43                         break;
44                         
45                     // whitespace end
46                     fullPos += pos;
47                     string = string.substr(pos);
48                     
49                     ////console.log("fullPos: " + fullPos);
50                     this.startWordOffsets.push(fullPos);
51                     
52                     i++;
53                 }
54                 //console.log("startWordOffsets: ", this.startWordOffsets);
55                 //console.groupEnd();
56             };
57             AttributedStringIterator.prototype = {
58                 getEndIndex: function(pos){
59                     if (typeof(pos) == "undefined")
60                         return this.endIndex;
61                         
62                     var string = this.text.substr(pos, this.endIndex - pos);
63                     
64                     var posEndOfLine = string.search(/[\n]/);
65                     if (posEndOfLine == -1)
66                         return this.endIndex;
67                     else
68                         return pos + posEndOfLine;
69                 },
70                 getBeginIndex: function(){
71                     return this.beginIndex;
72                 },
73                 isWhitespace: function(pos){
74                     var str = this.text[pos];
75                     var whitespaceChars = " \t\n\f";
76                     
77                     return (whitespaceChars.indexOf(str) != -1);
78                 },
79                 isNewLine: function(pos){
80                     var str = this.text[pos];
81                     var whitespaceChars = "\n";
82                     
83                     return (whitespaceChars.indexOf(str) != -1);
84                 },
85                 preceding: function(pos){
86                     //console.group("[AttributedStringIterator.preceding]");
87                     for(var i in this.startWordOffsets) {
88                         var startWordOffset = this.startWordOffsets[i];
89                         if (pos < startWordOffset && i>0) {
90                             //console.log("startWordOffset: " + this.startWordOffsets[i-1]);
91                             //console.groupEnd();
92                             return this.startWordOffsets[i-1];
93                         }
94                     }
95                     //console.log("pos: " + pos);
96                     //console.groupEnd();
97                     return this.startWordOffsets[i];
98                 },
99                 following: function(pos){
100                     //console.group("[AttributedStringIterator.following]");
101                     for(var i in this.startWordOffsets) {
102                         var startWordOffset = this.startWordOffsets[i];
103                         if (pos < startWordOffset && i>0) {
104                             //console.log("startWordOffset: " + this.startWordOffsets[i]);
105                             //console.groupEnd();
106                             return this.startWordOffsets[i];
107                         }
108                     }
109                     //console.log("pos: " + pos);
110                     //console.groupEnd();
111                     return this.startWordOffsets[i];
112                 },
113                 ltrim: function(str){
114                     var patt2=/^\s+/g;
115                     return str.replace(patt2, "");
116                 }, 
117                 rtrim: function(str){
118                     var patt2=/\s+$/g;
119                     return str.replace(patt2, "");
120                 },
121                 getLayout: function(start, limit){
122                     return this.text.substr(start, limit - start);
123                 },
124                 getCharAtPos: function(pos) {
125                     return this.text[pos];
126                 }
127             };
128
129         var LineBreakMeasurer = function(paper, x, y, text, fontAttrs){
130                 this.paper = paper;
131                 this.text = new AttributedStringIterator(text);
132                 this.fontAttrs = fontAttrs;
133                 
134                 if (this.text.getEndIndex() - this.text.getBeginIndex() < 1) {
135                     throw {message: "Text must contain at least one character.", code: "IllegalArgumentException"};
136                 }
137                 
138                 //this.measurer = new TextMeasurer(paper, this.text, this.fontAttrs);
139                 this.limit = this.text.getEndIndex();
140                 this.pos = this.start = this.text.getBeginIndex();
141                 
142                 this.rafaelTextObject = this.paper.text(x, y, this.text.text).attr(fontAttrs).attr("text-anchor", "start");
143                 this.svgTextObject = this.rafaelTextObject[0];
144             };
145             LineBreakMeasurer.prototype = {
146                 nextOffset: function(wrappingWidth, offsetLimit, requireNextWord) {
147                     //console.group("[nextOffset]");
148                     var nextOffset = this.pos;
149                     if (this.pos < this.limit) {
150                         if (offsetLimit <= this.pos) {
151                             throw {message: "offsetLimit must be after current position", code: "IllegalArgumentException"};
152                         }
153                         
154                         var charAtMaxAdvance = this.getLineBreakIndex(this.pos, wrappingWidth);
155                         //charAtMaxAdvance --;
156                         //console.log("charAtMaxAdvance:", charAtMaxAdvance, ", [" + this.text.getCharAtPos(charAtMaxAdvance) + "]");
157                         
158                         if (charAtMaxAdvance == this.limit) {
159                             nextOffset = this.limit;
160                             //console.log("charAtMaxAdvance == this.limit");
161                         } else if (this.text.isNewLine(charAtMaxAdvance)) {
162                             //console.log("isNewLine");
163                             nextOffset = charAtMaxAdvance+1;
164                         } else if (this.text.isWhitespace(charAtMaxAdvance)) {
165                             // TODO: find next noSpaceChar
166                             //return nextOffset;
167                             nextOffset = this.text.following(charAtMaxAdvance);
168                         } else {
169                             // Break is in a word;  back up to previous break.
170                             /*
171                             var testPos = charAtMaxAdvance + 1;
172                             if (testPos == this.limit) {
173                                 console.error("hbz...");
174                             } else {
175                                 nextOffset = this.text.preceding(charAtMaxAdvance);
176                             }
177                             */
178                             nextOffset = this.text.preceding(charAtMaxAdvance);
179                             
180                             if (nextOffset <= this.pos) {
181                                 nextOffset = Math.max(this.pos+1, charAtMaxAdvance);
182                             }
183                         }
184                     }
185                     if (nextOffset > offsetLimit) {
186                         nextOffset = offsetLimit;
187                     }
188                     //console.log("nextOffset: " + nextOffset);
189                     //console.groupEnd();
190                     return nextOffset;
191                 },
192                 nextLayout: function(wrappingWidth) {
193                     //console.groupCollapsed("[nextLayout]");
194                     if (this.pos < this.limit) {
195                         var requireNextWord = false;
196                         var layoutLimit = this.nextOffset(wrappingWidth, this.limit, requireNextWord);
197                         //console.log("layoutLimit:", layoutLimit);
198                         if (layoutLimit == this.pos) {
199                             //console.groupEnd();
200                             return null;
201                         }
202                         var result = this.text.getLayout(this.pos, layoutLimit);
203                         //console.log("layout: \"" + result + "\"");
204                         
205                         // remove end of line
206                         
207                         //var posEndOfLine = this.text.getEndIndex(this.pos);
208                         //if (posEndOfLine < result.length)
209                         //    result = result.substr(0, posEndOfLine);
210                         
211                         this.pos = layoutLimit;
212                         
213                         //console.groupEnd();
214                         return result;
215                     } else {
216                         //console.groupEnd();
217                         return null;
218                     }
219                 },
220                 getLineBreakIndex: function(pos, wrappingWidth) {
221                     //console.group("[getLineBreakIndex]");
222                     //console.log("pos:"+pos + ", text: \""+ this.text.text.replace(/\n/g, "_").substr(pos, 1) + "\"");
223                     
224                     var bb = this.rafaelTextObject.getBBox();
225                     
226                     var charNum = -1;
227                     try {
228                         var svgPoint = this.svgTextObject.getStartPositionOfChar(pos);
229                         //var dot = this.paper.ellipse(svgPoint.x, svgPoint.y, 1, 1).attr({"stroke-width": 0, fill: Color.blue});
230                         svgPoint.x = svgPoint.x + wrappingWidth;
231                         //svgPoint.y = bb.y;
232                         //console.log("svgPoint:", svgPoint);
233                     
234                         //var dot = this.paper.ellipse(svgPoint.x, svgPoint.y, 1, 1).attr({"stroke-width": 0, fill: Color.red});
235                     
236                         charNum = this.svgTextObject.getCharNumAtPosition(svgPoint);
237                     } catch (e){
238                         console.warn("getStartPositionOfChar error, pos:" + pos);
239                         /*
240                         var testPos = pos + 1;
241                         if (testPos < this.limit) {
242                             return testPos
243                         }
244                         */
245                     }
246                     //console.log("charNum:", charNum);
247                     if (charNum == -1) {
248                         //console.groupEnd();
249                         return this.text.getEndIndex(pos);
250                     } else {
251                         // When case there is new line between pos and charnum then use this new line
252                         var newLineIndex = this.text.getEndIndex(pos);
253                         if (newLineIndex < charNum ) {
254                             console.log("newLineIndex <= charNum, newLineIndex:"+newLineIndex+", charNum:"+charNum, "\"" + this.text.text.substr(newLineIndex+1).replace(/\n/g, "?") + "\"");
255                             //console.groupEnd();
256                             
257                             return newLineIndex;
258                         }
259                             
260                         //var charAtMaxAdvance  = this.text.text.substring(charNum, charNum + 1);
261                         var charAtMaxAdvance  = this.text.getCharAtPos(charNum);
262                         //console.log("!!charAtMaxAdvance: " + charAtMaxAdvance);
263                         //console.groupEnd();
264                         return charNum;
265                     }
266                 }, 
267                 getPosition: function() {
268                     return this.pos;
269                 }
270             };