define("dojox/gfx/_gfxBidiSupport", ["./_base", "dojo/_base/lang","dojo/_base/sniff", "dojo/dom", "dojo/_base/html", "dojo/_base/array", "./utils", "./shape", "dojox/string/BidiEngine"], function(g, lang, has, dom, html, arr, utils, shapeLib, BidiEngine){ lang.getObject("dojox.gfx._gfxBidiSupport", true); /*===== // Prevent changes here from masking the definitions in _base.js from the doc parser var origG = g; g = {}; =====*/ switch (g.renderer){ case 'vml': g.isVml = true; break; case 'svg': g.isSvg = true; if(g.svg.useSvgWeb){ g.isSvgWeb = true; } break; case 'silverlight': g.isSilverlight = true; break; case 'canvas': g.isCanvas = true; break; } var bidi_const = { LRM : '\u200E', LRE : '\u202A', PDF : '\u202C', RLM : '\u200f', RLE : '\u202B' }; /*===== g = origG; =====*/ // the object that performs text transformations. var bidiEngine = new BidiEngine(); lang.extend(g.shape.Surface, { // textDir: String // Will be used as default for Text/TextPath/Group objects that created by this surface // and textDir wasn't directly specified for them, though the bidi support was loaded. // Can be set in two ways: // // 1. When the surface is created and textDir value passed to it as fourth // parameter. // 2. Using the setTextDir(String) function, when this function is used the value // of textDir propagates to all of it's children and the children of children (for Groups) etc. textDir: "", setTextDir: function(/*String*/newTextDir){ // summary: // Used for propagation and change of textDir. // newTextDir will be forced as textDir for all of it's children (Group/Text/TextPath). setTextDir(this, newTextDir); }, getTextDir: function(){ return this.textDir; } }); lang.extend(g.Group, { // textDir: String // Will be used for inheritance, or as default for text objects // that textDir wasn't directly specified for them but the bidi support was required. textDir: "", setTextDir: function(/*String*/newTextDir){ // summary: // Used for propagation and change of textDir. // newTextDir will be forced as textDir for all of it's children (Group/Text/TextPath). setTextDir(this, newTextDir); }, getTextDir: function(){ return this.textDir; } }); lang.extend(g.Text, { // summary: // Overrides some of dojox/gfx.Text properties, and adds some // for bidi support. // textDir: String // Used for displaying bidi scripts in right layout. // Defines the base direction of text that displayed, can have 3 values: // // 1. "ltr" - base direction is left to right. // 2. "rtl" - base direction is right to left. // 3. "auto" - base direction is contextual (defined by first strong character). textDir: "", formatText: function (/*String*/ text, /*String*/ textDir){ // summary: // Applies the right transform on text, according to renderer. // text: // the string for manipulation, by default return value. // textDir: // Text direction. // Can be: // // 1. "ltr" - for left to right layout. // 2. "rtl" - for right to left layout // 3. "auto" - for contextual layout: the first strong letter decides the direction. // description: // Finds the right transformation that should be applied on the text, according to renderer. // Was tested in: // // Renderers (browser for testing): // // - canvas (FF, Chrome, Safari), // - vml (IE), // - svg (FF, Chrome, Safari, Opera), // - silverlight (IE, Chrome, Safari, Opera), // - svgWeb(FF, Chrome, Safari, Opera, IE). // // Browsers [browser version that was tested]: // // - IE [6,7,8], FF [3.6], // - Chrome (latest for March 2011), // - Safari [5.0.3], // - Opera [11.01]. if(textDir && text && text.length > 1){ var sourceDir = "ltr", targetDir = textDir; if(targetDir == "auto"){ //is auto by default if(g.isVml){ return text; } targetDir = bidiEngine.checkContextual(text); } if(g.isVml){ sourceDir = bidiEngine.checkContextual(text); if(targetDir != sourceDir){ if(targetDir == "rtl"){ return !bidiEngine.hasBidiChar(text) ? bidiEngine.bidiTransform(text,"IRNNN","ILNNN") : bidi_const.RLM + bidi_const.RLM + text; }else{ return bidi_const.LRM + text; } } return text; } if(g.isSvgWeb){ if(targetDir == "rtl"){ return bidiEngine.bidiTransform(text,"IRNNN","ILNNN"); } return text; } if(g.isSilverlight){ return (targetDir == "rtl") ? bidiEngine.bidiTransform(text,"IRNNN","VLYNN") : bidiEngine.bidiTransform(text,"ILNNN","VLYNN"); } if(g.isCanvas){ return (targetDir == "rtl") ? bidi_const.RLE + text + bidi_const.PDF : bidi_const.LRE + text + bidi_const.PDF; } if(g.isSvg){ if(has("ff") < 4){ return (targetDir == "rtl") ? bidiEngine.bidiTransform(text,"IRYNN","VLNNN") : bidiEngine.bidiTransform(text,"ILYNN","VLNNN"); }else{ return bidi_const.LRM + (targetDir == "rtl" ? bidi_const.RLE : bidi_const.LRE) + text + bidi_const.PDF; } } } return text; }, bidiPreprocess: function(newShape){ return newShape; } }); lang.extend(g.TextPath, { // textDir: String // Used for displaying bidi scripts in right layout. // Defines the base direction of text that displayed, can have 3 values: // // 1. "ltr" - base direction is left to right. // 2. "rtl" - base direction is right to left. // 3. "auto" - base direction is contextual (defined by first strong character). textDir: "", formatText: function (/*String*/text, /*String*/textDir){ // summary: // Applies the right transform on text, according to renderer. // text: // the string for manipulation, by default return value. // textDir: // text direction direction. // Can be: // // 1. "ltr" - for left to right layout. // 2. "rtl" - for right to left layout // 3. "auto" - for contextual layout: the first strong letter decides the direction. // description: // Finds the right transformation that should be applied on the text, according to renderer. // Was tested in: // // Renderers: // canvas (FF, Chrome, Safari), vml (IE), svg (FF, Chrome, Safari, Opera), silverlight (IE8), svgWeb(FF, Chrome, Safari, Opera, IE). // // Browsers: // IE [6,7,8], FF [3.6], Chrome (latest for February 2011), Safari [5.0.3], Opera [11.01]. if(textDir && text && text.length > 1){ var sourceDir = "ltr", targetDir = textDir; if(targetDir == "auto"){ //is auto by default if(g.isVml){ return text; } targetDir = bidiEngine.checkContextual(text); } if(g.isVml){ sourceDir = bidiEngine.checkContextual(text); if(targetDir != sourceDir){ if(targetDir == "rtl"){ return !bidiEngine.hasBidiChar(text) ? bidiEngine.bidiTransform(text,"IRNNN","ILNNN") : bidi_const.RLM + bidi_const.RLM + text; }else{ return bidi_const.LRM + text; } } return text; } if(g.isSvgWeb){ if(targetDir == "rtl"){ return bidiEngine.bidiTransform(text,"IRNNN","ILNNN"); } return text; } //unlike the g.Text that is rendered in logical layout for Bidi scripts. //for g.TextPath in svg always visual -> bidi script is unreadable (except Opera and FF start from version 4) if(g.isSvg){ if(has("opera") || has("ff") >= 4){ text = bidi_const.LRM + (targetDir == "rtl"? bidi_const.RLE : bidi_const.LRE) + text + bidi_const.PDF; }else{ text = (targetDir == "rtl") ? bidiEngine.bidiTransform(text,"IRYNN","VLNNN") : bidiEngine.bidiTransform(text,"ILYNN","VLNNN"); } } } return text; }, bidiPreprocess: function(newText){ if(newText && (typeof newText == "string")){ this.origText = newText; newText = this.formatText(newText,this.textDir); } return newText; } }); var extendMethod = function(shape, method, before, after){ // summary: // Some helper function. Used for extending methods of shape. // shape: Object // The shape we overriding it's method. // method: String // The method that is extended, the original method is called before or after // functions that passed to extendMethod. // before: function // If defined this function will be executed before the original method. // after: function // If defined this function will be executed after the original method. var old = shape.prototype[method]; shape.prototype[method] = function(){ var rBefore; if (before){ rBefore = before.apply(this, arguments); } var r = old.call(this, rBefore); if (after){ r = after.call(this, r, arguments); } return r; }; }; var bidiPreprocess = function(newText){ if (newText){ if (newText.textDir){ newText.textDir = validateTextDir(newText.textDir); } if (newText.text && (newText.text instanceof Array)){ newText.text = newText.text.join(","); } } if(newText && (newText.text != undefined || newText.textDir) && (this.textDir != newText.textDir || newText.text != this.origText)){ // store the original text. this.origText = (newText.text != undefined) ? newText.text : this.origText; if(newText.textDir){ this.textDir = newText.textDir; } newText.text = this.formatText(this.origText,this.textDir); } return this.bidiPreprocess(newText); }; // Instead of adding bidiPreprocess to all renders one by one // use the extendMethod, at first there's a need for bidi transformation // on text then call to original setShape. extendMethod(g.Text,"setShape", bidiPreprocess, null); extendMethod(g.TextPath,"setText", bidiPreprocess, null); var restoreText = function(origObj){ var obj = lang.clone(origObj); if (obj && this.origText){ obj.text = this.origText; } return obj; }; // Instead of adding restoreText to all renders one by one // use the extendMethod, at first get the shape by calling the original getShape, // than resrore original text (without the text transformations). extendMethod(g.Text, "getShape", null, restoreText); extendMethod(g.TextPath, "getText", null, restoreText); var groupTextDir = function(group, args){ var textDir; if (args && args[0]){ textDir = validateTextDir(args[0]); } group.setTextDir(textDir ? textDir : this.textDir); return group; // dojox/gfx.Group }; // In creation of Group there's a need to update it's textDir, // so instead of doing it in renders one by one (vml vs others) // use the extendMethod, at first the original createGroup is applied, the // groupTextDir which is setts Group's textDir as it's father's or if was defined // by user by this value. extendMethod(g.Surface, "createGroup", null, groupTextDir); extendMethod(g.Group, "createGroup", null, groupTextDir); var textDirPreprocess = function(text){ // inherit from surface / group if textDir is defined there if(text){ var textDir = text.textDir ? validateTextDir(text.textDir) : this.textDir; if(textDir){ text.textDir = textDir; } } return text; }; // In creation there's a need to some preprocess, // so instead of doing it in renders one by one (vml vs others) // use the extendMethod, at first the textDirPreprocess function handles the input // then the original createXXXXXX is applied. extendMethod(g.Surface,"createText", textDirPreprocess, null); extendMethod(g.Surface,"createTextPath", textDirPreprocess, null); extendMethod(g.Group,"createText", textDirPreprocess, null); extendMethod(g.Group,"createTextPath", textDirPreprocess, null); /*===== // don't mask definition of original createSurface() function from doc parser g = {}; =====*/ g.createSurface = function(parentNode, width, height, textDir) { var s = g[g.renderer].createSurface(parentNode, width, height); var tDir = validateTextDir(textDir); if(g.isSvgWeb){ s.textDir = tDir ? tDir : html.style(dom.byId(parentNode),"direction"); return s; } // if textDir was defined use it, else get default value. //s.textDir = tDir ? tDir : html.style(s.rawNode,"direction"); if(g.isVml || g.isSvg || g.isCanvas){ s.textDir = tDir ? tDir : html.style(s.rawNode,"direction"); } if(g.isSilverlight){ // allow this once rawNode will be able for the silverlight //s.textDir = tDir ? tDir : dojo.style(s.rawNode,"direction"); s.textDir = tDir ? tDir : html.style(s._nodes[1],"direction"); } return s; }; /*===== g = origG; =====*/ // some helper functions function setTextDir(/*Object*/ obj, /*String*/ newTextDir){ var tDir = validateTextDir(newTextDir); if (tDir){ g.utils.forEach(obj,function(e){ if(e instanceof g.Surface || e instanceof g.Group){ e.textDir = tDir; } if(e instanceof g.Text){ e.setShape({textDir: tDir}); } if(e instanceof g.TextPath){ e.setText({textDir: tDir}) } }, obj); } return obj; } function validateTextDir(textDir){ var validValues = ["ltr","rtl","auto"]; if (textDir){ textDir = textDir.toLowerCase(); if (arr.indexOf(validValues, textDir) < 0){ return null; } } return textDir; } return g; // return gfx api augmented with bidi support });