1 /* 2 Copyright 2008-2011 3 Matthias Ehmann, 4 Michael Gerhaeuser, 5 Carsten Miller, 6 Bianca Valentin, 7 Alfred Wassermann, 8 Peter Wilfahrt 9 10 This file is part of JSXGraph. 11 12 JSXGraph is free software: you can redistribute it and/or modify 13 it under the terms of the GNU Lesser General Public License as published by 14 the Free Software Foundation, either version 3 of the License, or 15 (at your option) any later version. 16 17 JSXGraph is distributed in the hope that it will be useful, 18 but WITHOUT ANY WARRANTY; without even the implied warranty of 19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 GNU Lesser General Public License for more details. 21 22 You should have received a copy of the GNU Lesser General Public License 23 along with JSXGraph. If not, see <http://www.gnu.org/licenses/>. 24 */ 25 26 /** 27 * @fileoverview In this file the Text element is defined. 28 */ 29 30 31 /** 32 * Construct and handle texts. 33 * @class Text: On creation the GEONExT syntax 34 * of <value>-terms 35 * are converted into JavaScript syntax. 36 * The coordinates can be relative to the coordinates of an element "element". 37 * @constructor 38 * @return A new geometry element Text 39 */ 40 JXG.Text = function (board, contentStr, element, coords, id, name, digits, isLabel, display, layer) { 41 this.constructor(); 42 43 this.type = JXG.OBJECT_TYPE_TEXT; 44 this.elementClass = JXG.OBJECT_CLASS_OTHER; 45 46 this.init(board, id, name); 47 this.contentStr = contentStr; 48 this.plaintextStr = ''; 49 50 /** 51 * Set the display layer. 52 */ 53 if (layer == null) layer = board.options.layer['text']; 54 this.layer = layer; 55 56 /** 57 * There is choice between 'html' and 'internal' 58 * 'internal' is the text element of SVG and the textpath element 59 * of VML. 60 */ 61 this.display = display || 'html'; 62 63 if((typeof isLabel != 'undefined') && (isLabel != null)) { 64 this.isLabel = isLabel; 65 } 66 else { 67 this.isLabel = false; 68 } 69 70 /** 71 * The text color of the given text. 72 * @type {string} 73 * @name JXG.Text#strokeColor 74 */ 75 this.visProp['strokeColor'] = this.board.options.text.strokeColor; 76 /** 77 * The text opacity of the given text. 78 * @type {string} 79 * @name JXG.Text#strokeOpacity 80 */ 81 /** 82 * The font size of the given text. 83 * @type {string} 84 * @name JXG.Text#fontSize 85 * @default {@link JXG.Options.fontSize} 86 */ 87 //this.visProp['fontSize'] = this.board.options.text.fontSize; 88 89 this.visProp['visible'] = true; 90 //this.show = true; // noch noetig? BV 91 92 if (digits!=null) { 93 this.digits = digits; 94 } else { 95 this.digits = 2; 96 } 97 98 /** 99 * Coordinates of the text. 100 * @ private 101 * @type JXG.Coords 102 */ 103 if ((this.element = this.board.objects[element])){ 104 var anchor; 105 if(!this.isLabel) { 106 anchor = this.element.getTextAnchor(); 107 } 108 else { 109 anchor = this.element.getLabelAnchor(); 110 } 111 this.element.addChild(this); 112 this.relativeCoords = new JXG.Coords(JXG.COORDS_BY_SCREEN, [parseFloat(coords[0]),parseFloat(coords[1])],this.board); 113 this.coords = new JXG.Coords(JXG.COORDS_BY_SCREEN, 114 [this.relativeCoords.scrCoords[1]+anchor.scrCoords[1], 115 this.relativeCoords.scrCoords[2]+anchor.scrCoords[2]], this.board); 116 } else { 117 this.X = JXG.createFunction(coords[0],this.board,''); 118 this.Y = JXG.createFunction(coords[1],this.board,''); 119 this.coords = new JXG.Coords(JXG.COORDS_BY_USER, [this.X(),this.Y()], this.board); 120 var fs = 'this.coords.setCoordinates(JXG.COORDS_BY_USER,[this.X(),this.Y()]);'; 121 this.updateCoords = new Function('',fs); 122 } 123 124 if (typeof this.contentStr=='function') { 125 this.updateText = function() { this.plaintextStr = this.contentStr(); }; 126 } else { 127 var plaintext; 128 if (typeof this.contentStr=='number') { 129 plaintext = (this.contentStr).toFixed(this.digits); 130 } else { 131 if (this.board.options.text.useASCIIMathML) { 132 plaintext = "'`"+this.contentStr+"`'"; // Convert via ASCIIMathML 133 } else { 134 plaintext = this.generateTerm(this.contentStr); // Converts GEONExT syntax into JavaScript string 135 } 136 } 137 this.updateText = new Function('this.plaintextStr = ' + plaintext + ';'); 138 } 139 this.updateText(); // First evaluation of the string. 140 // Needed for display='internal' and Canvas 141 if(!this.isLabel) { 142 this.id = this.board.setId(this, 'T'); 143 this.board.renderer.drawText(this); 144 if(!this.visProp['visible']) { 145 this.board.renderer.hide(this); 146 } 147 } 148 if (typeof this.contentStr=='string') { 149 this.notifyParents(this.contentStr); 150 } 151 this.size = [1.0,1.0]; 152 }; 153 JXG.Text.prototype = new JXG.GeometryElement(); 154 155 /** 156 * @private 157 * Empty function (for the moment). It is needed for highlighting 158 * @param {int} x 159 * @param {int} y Find closest point on the text to (xy) 160 * @return Always returns false 161 */ 162 JXG.Text.prototype.hasPoint = function (x,y) { 163 return false; 164 }; 165 166 /** 167 * Overwrite the text. 168 * @param {string,function} str 169 * @return {object} reference to the text object. 170 */ 171 JXG.Text.prototype.setText = function(text) { 172 var plaintext; 173 if (JXG.isNumber(text)) { 174 plaintext = (text).toFixed(this.digits); 175 this.updateText = new Function('this.plaintextStr = ' + plaintext + ';'); 176 } else if (JXG.isFunction(text)) { 177 this.updateText = function() { this.plaintextStr = text(); }; 178 } else { 179 plaintext = this.generateTerm(text); // Converts GEONExT syntax into JavaScript string 180 this.updateText = new Function('this.plaintextStr = ' + plaintext + ';'); 181 } 182 this.updateText(); 183 this.updateSize(); 184 return this; 185 }; 186 187 /** 188 * Recompute the width and the height of the text box. 189 * Update array this.size with pixel values. 190 * The result may differ from browser to browser 191 * by some pixels. 192 * In IE and canvas we use a very crude estimation of the dimensions of 193 * the textbox. 194 * In JSXGraph this.size is necessary for applying rotations in IE and 195 * for aligning text. 196 * @return {} 197 */ 198 JXG.Text.prototype.updateSize = function () { 199 // Here comes a very crude estimation of the dimensions of 200 // the textbox. It is only necessary for the IE. 201 if (this.display=='html' && this.board.renderer.type!='vml') { 202 this.size = [this.rendNode.offsetWidth, this.rendNode.offsetHeight]; 203 } else if (this.display=='internal' && this.board.renderer.type=='svg') { 204 this.size = [this.rendNode.getBBox().width, this.rendNode.getBBox().height]; 205 } else if (this.board.renderer.type=='vml' || (this.display=='internal' && this.board.renderer.type=='canvas')) { 206 this.size = [parseFloat(this.visProp['fontSize'])*this.plaintextStr.length*0.45,parseFloat(this.visProp['fontSize'])*0.9] 207 } 208 }; 209 210 /** 211 * Return the width of the text element. 212 * @return {array} [width, height] in pixel 213 */ 214 JXG.Text.prototype.getSize = function () { 215 return this.size; 216 }; 217 218 /** 219 * Set the text to new, fixed coordinates. 220 * @param {number} x 221 * @param {number} y 222 * @return {object} reference to the text object. 223 */ 224 JXG.Text.prototype.setCoords = function (x,y) { 225 this.X = function() { return x; }; 226 this.Y = function() { return y; }; 227 this.coords = new JXG.Coords(JXG.COORDS_BY_USER, [x,y], this.board); 228 return this; 229 }; 230 231 /** 232 * Evaluates the text. 233 * Then, the update function of the renderer 234 * is called. 235 */ 236 JXG.Text.prototype.update = function () { 237 var anchor, plainOld; 238 if (this.needsUpdate && !this.frozen) { 239 if (this.relativeCoords){ 240 anchor; 241 if(!this.isLabel) { 242 anchor = this.element.getTextAnchor(); 243 } 244 else { 245 anchor = this.element.getLabelAnchor(); 246 } 247 this.coords.setCoordinates(JXG.COORDS_BY_SCREEN, 248 [this.relativeCoords.scrCoords[1]+anchor.scrCoords[1], 249 this.relativeCoords.scrCoords[2]+anchor.scrCoords[2]]); 250 } else { 251 this.updateCoords(); 252 } 253 } 254 if (this.needsUpdate) { 255 this.updateText(); 256 this.updateSize(); 257 this.updateTransform(); 258 } 259 return this; 260 }; 261 262 /** 263 * The update function of the renderer 264 * is called. 265 * @private 266 */ 267 JXG.Text.prototype.updateRenderer = function () { 268 if (this.needsUpdate) { 269 this.board.renderer.updateText(this); 270 this.needsUpdate = false; 271 } 272 return this; 273 }; 274 275 JXG.Text.prototype.updateTransform = function () { 276 if (this.transformations.length==0) { 277 return; 278 } 279 for (var i=0;i<this.transformations.length;i++) { 280 this.transformations[i].update(); 281 } 282 }; 283 284 /** 285 * Converts the GEONExT syntax of the <value> terms into JavaScript. 286 * Also, all Objects whose name appears in the term are searched and 287 * the text is added as child to these objects. 288 * @private 289 * @see Algebra 290 * @see #geonext2JS. 291 */ 292 JXG.Text.prototype.generateTerm = function (contentStr) { 293 var res = null; 294 var elements = this.board.elementsByName; 295 var plaintext = '""'; 296 contentStr = contentStr.replace(/\r/g,''); 297 contentStr = contentStr.replace(/\n/g,''); 298 contentStr = contentStr.replace(/\"/g,'\\"'); 299 contentStr = contentStr.replace(/\'/g,"\\'"); 300 contentStr = contentStr.replace(/&arc;/g,'∠'); 301 contentStr = contentStr.replace(/<arc\s*\/>/g,'∠'); 302 contentStr = contentStr.replace(/<sqrt\s*\/>/g,'√'); 303 304 // Convert GEONExT syntax into JavaScript syntax 305 var i; 306 //var i = contentStr.indexOf('<mp>'); 307 //contentStr = contentStr.slice(i+4); 308 //i = contentStr.indexOf('</mp>'); 309 //contentStr = contentStr.slice(0,i); 310 311 i = contentStr.indexOf('<value>'); 312 var j = contentStr.indexOf('</value>'); 313 if (i>=0) { 314 while (i>=0) { 315 plaintext += ' + "'+ JXG.GeonextParser.replaceSub(JXG.GeonextParser.replaceSup(contentStr.slice(0,i))) + '"'; 316 var term = contentStr.slice(i+7,j); 317 var res = JXG.GeonextParser.geonext2JS(term, this.board); 318 res = res.replace(/\\"/g,'"'); 319 res = res.replace(/\\'/g,"'"); 320 if (res.indexOf('toFixed')<0) { // GEONExT-Hack: apply rounding once only. 321 plaintext += '+('+ res + ').toFixed('+(this.digits)+')'; 322 } else { 323 plaintext += '+('+ res + ')'; 324 } 325 contentStr = contentStr.slice(j+8); 326 i = contentStr.indexOf('<value>'); 327 j = contentStr.indexOf('</value>'); 328 } 329 } //else { 330 plaintext += ' + "' + JXG.GeonextParser.replaceSub(JXG.GeonextParser.replaceSup(contentStr)) + '"'; 331 //} 332 plaintext = plaintext.replace(/<overline>/g,'<span style=text-decoration:overline>'); 333 plaintext = plaintext.replace(/<\/overline>/g,'</span>'); 334 plaintext = plaintext.replace(/<arrow>/g,'<span style=text-decoration:overline>'); 335 plaintext = plaintext.replace(/<\/arrow>/g,'</span>'); 336 /* i = plaintext.indexOf('<name>'); 337 j = plaintext.indexOf('</name>'); 338 while (i>=0) { 339 var head = plaintext.slice(0,i+6); 340 var mid = plaintext.slice(i+6,j); 341 var tail = plaintext.slice(j); 342 mid = JXG.GeonextParser.replaceSub(JXG.GeonextParser.replaceSup(mid)); 343 plaintext = head + mid + tail; 344 i = plaintext.indexOf('<name>',i+7); 345 j = plaintext.indexOf('</name>',i+7); 346 } 347 */ 348 plaintext = plaintext.replace(/&/g,'&'); // This should replace π by π 349 return plaintext; 350 }; 351 352 /** 353 * Finds dependencies in a given term and notifies the parents by adding the 354 * dependent object to the found objects child elements. 355 * @param {String} term String containing dependencies for the given object. 356 * @private 357 */ 358 JXG.Text.prototype.notifyParents = function (contentStr) { 359 var res = null; 360 var elements = this.board.elementsByName; 361 362 do { 363 var search = /<value>([\w\s\*\/\^\-\+\(\)\[\],<>=!]+)<\/value>/; 364 res = search.exec(contentStr); 365 if (res!=null) { 366 JXG.GeonextParser.findDependencies(this,res[1],this.board); 367 contentStr = contentStr.substr(res.index); 368 contentStr = contentStr.replace(search,''); 369 } 370 } while (res!=null); 371 return this; 372 }; 373 374 /** 375 * @class This element is used to provide a constructor for text, which is just a wrapper for element {@link Text}. 376 * @pseudo 377 * @description 378 * @name Text 379 * @augments JXG.GeometryElement 380 * @constructor 381 * @type JXG.Text 382 * 383 * @param {number,function_number,function_String,function} x,y,str Parent elements for text elements. 384 * <p> 385 * x and y are the coordinates of the lower left corner of the text box. The position of the text is fixed, 386 * x and y are numbers. The position is variable if x or y are functions. 387 * <p> 388 * The text to display may be given as string or as function returning a string. 389 * 390 * There is the attribute 'display' which takes the values 'html' or 'internal'. In case of 'html' a HTML division tag is created to display 391 * the text. In this case it is also possible to use ASCIIMathML. Incase of 'internal', a SVG or VML text element is used to display the text. 392 * @see JXG.Text 393 * @example 394 * // Create a fixed text at position [0,1]. 395 * var t1 = board.create('text',[0,1,"Hello World"]); 396 * </pre><div id="896013aa-f24e-4e83-ad50-7bc7df23f6b7" style="width: 300px; height: 300px;"></div> 397 * <script type="text/javascript"> 398 * var t1_board = JXG.JSXGraph.initBoard('896013aa-f24e-4e83-ad50-7bc7df23f6b7', {boundingbox: [-3, 6, 5, -3], axis: true, showcopyright: false, shownavigation: false}); 399 * var t1 = t1_board.create('text',[0,1,"Hello World"]); 400 * </script><pre> 401 * @example 402 * // Create a variable text at a variable position. 403 * var s = board.create('slider',[[0,4],[3,4],[-2,0,2]]); 404 * var graph = board.create('text', 405 * [function(x){ return s.Value();}, 1, 406 * function(){return "The value of s is"+s.Value().toFixed(2);} 407 * ] 408 * ); 409 * </pre><div id="5441da79-a48d-48e8-9e53-75594c384a1c" style="width: 300px; height: 300px;"></div> 410 * <script type="text/javascript"> 411 * var t2_board = JXG.JSXGraph.initBoard('5441da79-a48d-48e8-9e53-75594c384a1c', {boundingbox: [-3, 6, 5, -3], axis: true, showcopyright: false, shownavigation: false}); 412 * var s = t2_board.create('slider',[[0,4],[3,4],[-2,0,2]]); 413 * var t2 = t2_board.create('text',[function(x){ return s.Value();}, 1, function(){return "The value of s is "+s.Value().toFixed(2);}]); 414 * </script><pre> 415 */ 416 JXG.createText = function(board, parentArr, atts) { 417 atts = JXG.checkAttributes(atts,{layer:null,display:board.options.text.defaultDisplay,parent:null}); // 'html' or 'internal' 418 if(atts['parent'] != null) { atts['parent'] = atts['parent'].id;} 419 return new JXG.Text(board, parentArr[parentArr.length-1], atts['parent'], parentArr, atts['id'], atts['name'], atts['digits'], false, atts['display'],atts['layer']); 420 }; 421 422 JXG.JSXGraph.registerElement('text', JXG.createText); 423