/** * Word wrapping * * @author (Javascript) Dmitry Farafonov */ var AttributedStringIterator = function(text){ //this.text = this.rtrim(this.ltrim(text)); text = text.replace(/(\s)+/, " "); this.text = this.rtrim(text); /* if (beginIndex < 0 || beginIndex > endIndex || endIndex > length()) { throw new IllegalArgumentException("Invalid substring range"); } */ this.beginIndex = 0; this.endIndex = this.text.length; this.currentIndex = this.beginIndex; //console.group("[AttributedStringIterator]"); var i = 0; var string = this.text; var fullPos = 0; //console.log("string: \"" + string + "\", length: " + string.length); this.startWordOffsets = []; this.startWordOffsets.push(fullPos); // TODO: remove i 1000 while (i<1000) { var pos = string.search(/[ \t\n\f-\.\,]/); if (pos == -1) break; // whitespace start fullPos += pos; string = string.substr(pos); ////console.log("fullPos: " + fullPos + ", pos: " + pos + ", string: ", string); // remove whitespaces var pos = string.search(/[^ \t\n\f-\.\,]/); if (pos == -1) break; // whitespace end fullPos += pos; string = string.substr(pos); ////console.log("fullPos: " + fullPos); this.startWordOffsets.push(fullPos); i++; } //console.log("startWordOffsets: ", this.startWordOffsets); //console.groupEnd(); }; AttributedStringIterator.prototype = { getEndIndex: function(pos){ if (typeof(pos) == "undefined") return this.endIndex; var string = this.text.substr(pos, this.endIndex - pos); var posEndOfLine = string.search(/[\n]/); if (posEndOfLine == -1) return this.endIndex; else return pos + posEndOfLine; }, getBeginIndex: function(){ return this.beginIndex; }, isWhitespace: function(pos){ var str = this.text[pos]; var whitespaceChars = " \t\n\f"; return (whitespaceChars.indexOf(str) != -1); }, isNewLine: function(pos){ var str = this.text[pos]; var whitespaceChars = "\n"; return (whitespaceChars.indexOf(str) != -1); }, preceding: function(pos){ //console.group("[AttributedStringIterator.preceding]"); for(var i in this.startWordOffsets) { var startWordOffset = this.startWordOffsets[i]; if (pos < startWordOffset && i>0) { //console.log("startWordOffset: " + this.startWordOffsets[i-1]); //console.groupEnd(); return this.startWordOffsets[i-1]; } } //console.log("pos: " + pos); //console.groupEnd(); return this.startWordOffsets[i]; }, following: function(pos){ //console.group("[AttributedStringIterator.following]"); for(var i in this.startWordOffsets) { var startWordOffset = this.startWordOffsets[i]; if (pos < startWordOffset && i>0) { //console.log("startWordOffset: " + this.startWordOffsets[i]); //console.groupEnd(); return this.startWordOffsets[i]; } } //console.log("pos: " + pos); //console.groupEnd(); return this.startWordOffsets[i]; }, ltrim: function(str){ var patt2=/^\s+/g; return str.replace(patt2, ""); }, rtrim: function(str){ var patt2=/\s+$/g; return str.replace(patt2, ""); }, getLayout: function(start, limit){ return this.text.substr(start, limit - start); }, getCharAtPos: function(pos) { return this.text[pos]; } }; var LineBreakMeasurer = function(paper, x, y, text, fontAttrs){ this.paper = paper; this.text = new AttributedStringIterator(text); this.fontAttrs = fontAttrs; if (this.text.getEndIndex() - this.text.getBeginIndex() < 1) { throw {message: "Text must contain at least one character.", code: "IllegalArgumentException"}; } //this.measurer = new TextMeasurer(paper, this.text, this.fontAttrs); this.limit = this.text.getEndIndex(); this.pos = this.start = this.text.getBeginIndex(); this.rafaelTextObject = this.paper.text(x, y, this.text.text).attr(fontAttrs).attr("text-anchor", "start"); this.svgTextObject = this.rafaelTextObject[0]; }; LineBreakMeasurer.prototype = { nextOffset: function(wrappingWidth, offsetLimit, requireNextWord) { //console.group("[nextOffset]"); var nextOffset = this.pos; if (this.pos < this.limit) { if (offsetLimit <= this.pos) { throw {message: "offsetLimit must be after current position", code: "IllegalArgumentException"}; } var charAtMaxAdvance = this.getLineBreakIndex(this.pos, wrappingWidth); //charAtMaxAdvance --; //console.log("charAtMaxAdvance:", charAtMaxAdvance, ", [" + this.text.getCharAtPos(charAtMaxAdvance) + "]"); if (charAtMaxAdvance == this.limit) { nextOffset = this.limit; //console.log("charAtMaxAdvance == this.limit"); } else if (this.text.isNewLine(charAtMaxAdvance)) { //console.log("isNewLine"); nextOffset = charAtMaxAdvance+1; } else if (this.text.isWhitespace(charAtMaxAdvance)) { // TODO: find next noSpaceChar //return nextOffset; nextOffset = this.text.following(charAtMaxAdvance); } else { // Break is in a word; back up to previous break. /* var testPos = charAtMaxAdvance + 1; if (testPos == this.limit) { console.error("hbz..."); } else { nextOffset = this.text.preceding(charAtMaxAdvance); } */ nextOffset = this.text.preceding(charAtMaxAdvance); if (nextOffset <= this.pos) { nextOffset = Math.max(this.pos+1, charAtMaxAdvance); } } } if (nextOffset > offsetLimit) { nextOffset = offsetLimit; } //console.log("nextOffset: " + nextOffset); //console.groupEnd(); return nextOffset; }, nextLayout: function(wrappingWidth) { //console.groupCollapsed("[nextLayout]"); if (this.pos < this.limit) { var requireNextWord = false; var layoutLimit = this.nextOffset(wrappingWidth, this.limit, requireNextWord); //console.log("layoutLimit:", layoutLimit); if (layoutLimit == this.pos) { //console.groupEnd(); return null; } var result = this.text.getLayout(this.pos, layoutLimit); //console.log("layout: \"" + result + "\""); // remove end of line //var posEndOfLine = this.text.getEndIndex(this.pos); //if (posEndOfLine < result.length) // result = result.substr(0, posEndOfLine); this.pos = layoutLimit; //console.groupEnd(); return result; } else { //console.groupEnd(); return null; } }, getLineBreakIndex: function(pos, wrappingWidth) { //console.group("[getLineBreakIndex]"); //console.log("pos:"+pos + ", text: \""+ this.text.text.replace(/\n/g, "_").substr(pos, 1) + "\""); var bb = this.rafaelTextObject.getBBox(); var charNum = -1; try { var svgPoint = this.svgTextObject.getStartPositionOfChar(pos); //var dot = this.paper.ellipse(svgPoint.x, svgPoint.y, 1, 1).attr({"stroke-width": 0, fill: Color.blue}); svgPoint.x = svgPoint.x + wrappingWidth; //svgPoint.y = bb.y; //console.log("svgPoint:", svgPoint); //var dot = this.paper.ellipse(svgPoint.x, svgPoint.y, 1, 1).attr({"stroke-width": 0, fill: Color.red}); charNum = this.svgTextObject.getCharNumAtPosition(svgPoint); } catch (e){ console.warn("getStartPositionOfChar error, pos:" + pos); /* var testPos = pos + 1; if (testPos < this.limit) { return testPos } */ } //console.log("charNum:", charNum); if (charNum == -1) { //console.groupEnd(); return this.text.getEndIndex(pos); } else { // When case there is new line between pos and charnum then use this new line var newLineIndex = this.text.getEndIndex(pos); if (newLineIndex < charNum ) { console.log("newLineIndex <= charNum, newLineIndex:"+newLineIndex+", charNum:"+charNum, "\"" + this.text.text.substr(newLineIndex+1).replace(/\n/g, "?") + "\""); //console.groupEnd(); return newLineIndex; } //var charAtMaxAdvance = this.text.text.substring(charNum, charNum + 1); var charAtMaxAdvance = this.text.getCharAtPos(charNum); //console.log("!!charAtMaxAdvance: " + charAtMaxAdvance); //console.groupEnd(); return charNum; } }, getPosition: function() { return this.pos; } };