You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							441 lines
						
					
					
						
							15 KiB
						
					
					
				
			
		
		
	
	
							441 lines
						
					
					
						
							15 KiB
						
					
					
					window.onload = function () { | 
						|
			var paper = Raphael("holder"); | 
						|
 | 
						|
			//var curve = paper.ellipse(100, 100, 1, 1).attr({"stroke-width": 0, fill: Color.red}); | 
						|
			 | 
						|
			var text = "Betty Botter bought some butter but, she said, the butter's bitter. If I put it in my batter, it will make my batter bitter. But a bit of better butter will make my batter better. So, she bought a bit of butter, better than her bitter butter, and she put it in her batter, and the batter was not bitter. It was better Betty Botter bought a bit better butter."; | 
						|
			var font = {font: "11px Arial", "font-style":"italic", opacity: 1, "fill": LABEL_COLOR, stroke: LABEL_COLOR, "stroke-width":.3}; | 
						|
			var font = {font: "11px Arial", opacity: 1, "fill": LABEL_COLOR}; | 
						|
			var boxWidth = 100 | 
						|
			 | 
						|
			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 TextMeasurer = function(paper, text, fontAttrs){ | 
						|
				this.text = text; | 
						|
				this.paper = paper; | 
						|
				this.fontAttrs = fontAttrs; | 
						|
				 | 
						|
				this.fStart = this.text.getBeginIndex(); | 
						|
 | 
						|
			}; | 
						|
			TextMeasurer.prototype = { | 
						|
				getLineBreakIndex: function(start, maxAdvance){ | 
						|
					var localStart = start - this.fStart; | 
						|
				}, | 
						|
				getLayout: function(){ | 
						|
				} | 
						|
			} | 
						|
			*/ | 
						|
			 | 
						|
			 | 
						|
			var LineBreakMeasurer = function(paper, 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(100, 100, 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; | 
						|
				} | 
						|
			}; | 
						|
			 | 
						|
			 | 
						|
			 | 
						|
			// ****** | 
						|
			function drawMultilineText(text, x, y, boxWidth, boxHeight, options) { | 
						|
				var TEXT_PADDING = 3; | 
						|
				var width = boxWidth - (2 * TEXT_PADDING); | 
						|
				if (boxHeight) | 
						|
					var height = boxHeight - (2 * TEXT_PADDING); | 
						|
			 | 
						|
				var layouts = []; | 
						|
				 | 
						|
				var measurer = new LineBreakMeasurer(paper, text, font); | 
						|
				var lineHeight = measurer.rafaelTextObject.getBBox().height; | 
						|
				console.log("text: ", text.replace(/\n/g, "↵")); | 
						|
				 | 
						|
				if (height) { | 
						|
					var availableLinesCount = parseInt(height/lineHeight); | 
						|
					console.log("availableLinesCount: " + availableLinesCount); | 
						|
				} | 
						|
				 | 
						|
				var i = 1; | 
						|
				while (measurer.getPosition() < measurer.text.getEndIndex()) { | 
						|
					var layout = measurer.nextLayout(width); | 
						|
					//console.log("LAYOUT: " + layout + ", getPosition: " + measurer.getPosition()); | 
						|
					 | 
						|
					if (layout != null) { | 
						|
						if (!availableLinesCount || i < availableLinesCount) { | 
						|
							layouts.push(layout); | 
						|
						} else { | 
						|
							layouts.push(fitTextToWidth(layout + "...", boxWidth)); | 
						|
							break; | 
						|
						} | 
						|
					} | 
						|
					i++; | 
						|
				}; | 
						|
				console.log(layouts); | 
						|
				 | 
						|
				measurer.rafaelTextObject.attr({"text": layouts.join("\n")}); | 
						|
				//measurer.rafaelTextObject.attr({"text-anchor": "end"}); | 
						|
				//measurer.rafaelTextObject.attr({"text-anchor": "middle"}); | 
						|
				if (options) | 
						|
					measurer.rafaelTextObject.attr({"text-anchor": options["text-anchor"]}); | 
						|
					 | 
						|
				var bb = measurer.rafaelTextObject.getBBox(); | 
						|
				//measurer.rafaelTextObject.attr({"x": x + boxWidth/2}); | 
						|
				if (options["vertical-align"] == "top") | 
						|
					measurer.rafaelTextObject.attr({"y": y + bb.height/2 + TEXT_PADDING}); | 
						|
				else | 
						|
					measurer.rafaelTextObject.attr({"y": y + height/2}); | 
						|
				//var bb = measurer.rafaelTextObject.getBBox(); | 
						|
				 | 
						|
				if (measurer.rafaelTextObject.attr("text-anchor") == "middle" ) | 
						|
					measurer.rafaelTextObject.attr("x",  x + boxWidth/2 + TEXT_PADDING/2); | 
						|
				else if (measurer.rafaelTextObject.attr("text-anchor") == "end" ) | 
						|
					measurer.rafaelTextObject.attr("x",  x + boxWidth + TEXT_PADDING/2); | 
						|
				else  | 
						|
					measurer.rafaelTextObject.attr("x", x + boxWidth/2 - bb.width/2 + TEXT_PADDING/2); | 
						|
				 | 
						|
				var boxStyle = {stroke: Color.LightSteelBlue2, "stroke-width": 1.0, "stroke-dasharray": "- "}; | 
						|
				/* | 
						|
				var box = paper.rect(x+.0 + boxWidth/2 - bb.width/2+ TEXT_PADDING/2, y + .5 + boxHeight/2 - bb.height/2, width, height).attr(boxStyle); | 
						|
				box.attr("height", bb.height); | 
						|
				*/ | 
						|
				//var box = paper.rect(bb.x - .5 + bb.width/2 + TEXT_PADDING, bb.y + bb.height/2, bb.width, bb.height).attr(boxStyle); | 
						|
				 | 
						|
				var textAreaCX = x + boxWidth/2; | 
						|
				var textAreaCY = y + height/2; | 
						|
				var dotLeftTop = paper.ellipse(x, y, 3, 3).attr({"stroke-width": 0, fill: Color.LightSteelBlue, stroke: "none"}); | 
						|
				var dotCenter = paper.ellipse(textAreaCX, textAreaCY, 3, 3).attr({fill: Color.LightSteelBlue2, stroke: "none"}); | 
						|
 | 
						|
				/* | 
						|
				// real bbox | 
						|
				var bb = measurer.rafaelTextObject.getBBox(); | 
						|
				var rect = paper.rect(bb.x+.5, bb.y + .5, bb.width, bb.height).attr({"stroke-width": 1}); | 
						|
				*/ | 
						|
				var boxStyle = {stroke: Color.LightSteelBlue2, "stroke-width": 1.0, "stroke-dasharray": "- "}; | 
						|
				var rect = paper.rect(x+.5, y + .5, boxWidth, boxHeight).attr(boxStyle); | 
						|
			} | 
						|
			 | 
						|
			 | 
						|
			 | 
						|
			 | 
						|
			/* | 
						|
			for (var i=0; i<1; i++) { | 
						|
				var t = text; | 
						|
				//var t = "Высококвалифицирова"; | 
						|
				 | 
						|
				var text = paper.text(300, 100, t).attr(font).attr("text-anchor", "start"); | 
						|
				var bbText = text.getBBox(); | 
						|
				paper.rect(300+.5, 100 + .5, bbText.width, bbText.height).attr({"stroke-width": 1}); | 
						|
				console.log("t: ", t.replace(/\n/g, "↵")); | 
						|
				 | 
						|
				while (measurer.getPosition() < measurer.text.getEndIndex()) { | 
						|
					var layout = measurer.nextLayout(width); | 
						|
					//console.log("LAYOUT: " + layout + ", getPosition: " + measurer.getPosition()); | 
						|
					if (layout != null) | 
						|
						layouts.push(layout); | 
						|
				}; | 
						|
				 | 
						|
				measurer.rafaelTextObject.attr("text", layouts.join("\n")); | 
						|
				var bb = measurer.rafaelTextObject.getBBox(); | 
						|
				var rect = paper.rect(bb.x+.5, bb.y + .5, bb.width, bb.height).attr({"stroke-width": 1}); | 
						|
				 | 
						|
				lay.push(layouts); | 
						|
				console.log(layouts); | 
						|
			} | 
						|
			*/ | 
						|
			 | 
						|
			 | 
						|
			var fitTextToWidth = function(original, width) { | 
						|
				var text = original; | 
						|
 | 
						|
				// TODO: move attr on parameters | 
						|
				var attr = {font: "11px Arial", opacity: 0}; | 
						|
				 | 
						|
				// remove length for "..." | 
						|
				var dots = paper.text(0, 0, "...").attr(attr).hide(); | 
						|
				var dotsBB = dots.getBBox(); | 
						|
				 | 
						|
				var maxWidth = width - dotsBB.width; | 
						|
				 | 
						|
				var textElement = paper.text(0, 0, text).attr(attr).hide(); | 
						|
				var bb = textElement.getBBox(); | 
						|
				 | 
						|
				// it's a little bit incorrect with "..." | 
						|
				while (bb.width > maxWidth && text.length > 0) { | 
						|
					text = text.substring(0, text.length - 1); | 
						|
					textElement.attr({"text": text}); | 
						|
					bb = textElement.getBBox(); | 
						|
				} | 
						|
 | 
						|
				// remove element from paper | 
						|
				textElement.remove(); | 
						|
				 | 
						|
				if (text != original) { | 
						|
					text = text + "..."; | 
						|
				} | 
						|
 | 
						|
				return text; | 
						|
			} | 
						|
			 | 
						|
			 | 
						|
			var x=100, y=90, height=20; | 
						|
			var options = {"text-anchor": "middle", "boxHeight": 150, "vertical-align": "top"}; | 
						|
			var options = {"boxHeight": 150, "vertical-align": "top"}; | 
						|
			drawMultilineText(text, x, y, 150, 100, options); | 
						|
	}; |