_gfxBidiSupport.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404
  1. define("dojox/gfx/_gfxBidiSupport", ["./_base", "dojo/_base/lang","dojo/_base/sniff", "dojo/dom", "dojo/_base/html", "dojo/_base/array",
  2. "./utils", "./shape", "dojox/string/BidiEngine"],
  3. function(g, lang, has, dom, html, arr, utils, shapeLib, BidiEngine){
  4. lang.getObject("dojox.gfx._gfxBidiSupport", true);
  5. /*===== g = dojox.gfx; =====*/
  6. switch (g.renderer){
  7. case 'vml':
  8. g.isVml = true;
  9. break;
  10. case 'svg':
  11. g.isSvg = true;
  12. if(g.svg.useSvgWeb){
  13. g.isSvgWeb = true;
  14. }
  15. break;
  16. case 'silverlight':
  17. g.isSilverlight = true;
  18. break;
  19. case 'canvas':
  20. g.isCanvas = true;
  21. break;
  22. }
  23. var bidi_const = {
  24. LRM : '\u200E',
  25. LRE : '\u202A',
  26. PDF : '\u202C',
  27. RLM : '\u200f',
  28. RLE : '\u202B'
  29. };
  30. // the object that performs text transformations.
  31. var bidiEngine = new BidiEngine();
  32. lang.extend(g.shape.Surface, {
  33. // textDir: String
  34. // Will be used as default for Text/TextPath/Group objects that created by this surface
  35. // and textDir wasn't directly specified for them, though the bidi support was loaded.
  36. // Can be setted in two ways:
  37. // 1. When the surface is created and textDir value passed to it as fourth
  38. // parameter.
  39. // 2. Using the setTextDir(String) function, when this function is used the value
  40. // of textDir propogates to all of it's children and the children of children (for Groups) etc.
  41. textDir: "",
  42. setTextDir: function(/*String*/newTextDir){
  43. // summary:
  44. // Used for propogation and change of textDir.
  45. // newTextDir will be forced as textDir for all of it's children (Group/Text/TextPath).
  46. setTextDir(this, newTextDir);
  47. },
  48. getTextDir: function(){
  49. return this.textDir;
  50. }
  51. });
  52. lang.extend(g.Group, {
  53. // textDir: String
  54. // Will be used for inheritance, or as default for text objects
  55. // that textDir wasn't directly specified for them but the bidi support was required.
  56. textDir: "",
  57. setTextDir: function(/*String*/newTextDir){
  58. // summary:
  59. // Used for propogation and change of textDir.
  60. // newTextDir will be forced as textDir for all of it's children (Group/Text/TextPath).
  61. setTextDir(this, newTextDir);
  62. },
  63. getTextDir: function(){
  64. return this.textDir;
  65. }
  66. });
  67. lang.extend(g.Text, {
  68. // summary:
  69. // Overrides some of dojox.gfx.Text properties, and adds some
  70. // for bidi support.
  71. // textDir: String
  72. // Used for displaying bidi scripts in right layout.
  73. // Defines the base direction of text that displayed, can have 3 values:
  74. // 1. "ltr" - base direction is left to right.
  75. // 2. "rtl" - base direction is right to left.
  76. // 3. "auto" - base direction is contextual (defined by first strong character).
  77. textDir: "",
  78. formatText: function (/*String*/ text, /*String*/ textDir){
  79. // summary:
  80. // Applies the right transform on text, according to renderer.
  81. // text:
  82. // the string for manipulation, by default return value.
  83. // textDir:
  84. // Text direction.
  85. // Can be:
  86. // 1. "ltr" - for left to right layout.
  87. // 2. "rtl" - for right to left layout
  88. // 3. "auto" - for contextual layout: the first strong letter decides the direction.
  89. // discription:
  90. // Finds the right transformation that should be applied on the text, according to renderer.
  91. // Was tested in:
  92. // Renderers (browser for testing):
  93. // canvas (FF, Chrome, Safari),
  94. // vml (IE),
  95. // svg (FF, Chrome, Safari, Opera),
  96. // silverlight (IE, Chrome, Safari, Opera),
  97. // svgWeb(FF, Chrome, Safari, Opera, IE).
  98. // Browsers [browser version that was tested]:
  99. // IE [6,7,8], FF [3.6],
  100. // Chrome (latest for March 2011),
  101. // Safari [5.0.3],
  102. // Opera [11.01].
  103. if(textDir && text && text.length > 1){
  104. var sourceDir = "ltr", targetDir = textDir;
  105. if(targetDir == "auto"){
  106. //is auto by default
  107. if(g.isVml){
  108. return text;
  109. }
  110. targetDir = bidiEngine.checkContextual(text);
  111. }
  112. if(g.isVml){
  113. sourceDir = bidiEngine.checkContextual(text);
  114. if(targetDir != sourceDir){
  115. if(targetDir == "rtl"){
  116. return !bidiEngine.hasBidiChar(text) ? bidiEngine.bidiTransform(text,"IRNNN","ILNNN") : bidi_const.RLM + bidi_const.RLM + text;
  117. }else{
  118. return bidi_const.LRM + text;
  119. }
  120. }
  121. return text;
  122. }
  123. if(g.isSvgWeb){
  124. if(targetDir == "rtl"){
  125. return bidiEngine.bidiTransform(text,"IRNNN","ILNNN");
  126. }
  127. return text;
  128. }
  129. if(g.isSilverlight){
  130. return (targetDir == "rtl") ? bidiEngine.bidiTransform(text,"IRNNN","VLYNN") : bidiEngine.bidiTransform(text,"ILNNN","VLYNN");
  131. }
  132. if(g.isCanvas){
  133. return (targetDir == "rtl") ? bidi_const.RLE + text + bidi_const.PDF : bidi_const.LRE + text + bidi_const.PDF;
  134. }
  135. if(g.isSvg){
  136. if(has("ff")){
  137. return (targetDir == "rtl") ? bidiEngine.bidiTransform(text,"IRYNN","VLNNN") : bidiEngine.bidiTransform(text,"ILYNN","VLNNN");
  138. }
  139. if(has("chrome") || has("safari") || has("opera")){
  140. return bidi_const.LRM + (targetDir == "rtl" ? bidi_const.RLE : bidi_const.LRE) + text + bidi_const.PDF;
  141. }
  142. }
  143. }
  144. return text;
  145. },
  146. bidiPreprocess: function(newShape){
  147. return newShape;
  148. }
  149. });
  150. lang.extend(g.TextPath, {
  151. // textDir: String
  152. // Used for displaying bidi scripts in right layout.
  153. // Defines the base direction of text that displayed, can have 3 values:
  154. // 1. "ltr" - base direction is left to right.
  155. // 2. "rtl" - base direction is right to left.
  156. // 3. "auto" - base direction is contextual (defined by first strong character).
  157. textDir: "",
  158. formatText: function (/*String*/text, /*String*/textDir){
  159. // summary:
  160. // Applies the right transform on text, according to renderer.
  161. // text: the string for manipulation, by default return value.
  162. // textDir: text direction direction.
  163. // Can be:
  164. // 1. "ltr" - for left to right layout.
  165. // 2. "rtl" - for right to left layout
  166. // 3. "auto" - for contextual layout: the first strong letter decides the direction.
  167. // discription:
  168. // Finds the right transformation that should be applied on the text, according to renderer.
  169. // Was tested in:
  170. // Renderers:
  171. // canvas (FF, Chrome, Safari), vml (IE), svg (FF, Chrome, Safari, Opera), silverlight (IE8), svgWeb(FF, Chrome, Safari, Opera, IE).
  172. // Browsers:
  173. // IE [6,7,8], FF [3.6], Chrome (latest for February 2011), Safari [5.0.3], Opera [11.01].
  174. if(textDir && text && text.length > 1){
  175. var sourceDir = "ltr", targetDir = textDir;
  176. if(targetDir == "auto"){
  177. //is auto by default
  178. if(g.isVml){
  179. return text;
  180. }
  181. targetDir = bidiEngine.checkContextual(text);
  182. }
  183. if(g.isVml){
  184. sourceDir = bidiEngine.checkContextual(text);
  185. if(targetDir != sourceDir){
  186. if(targetDir == "rtl"){
  187. return !bidiEngine.hasBidiChar(text) ? bidiEngine.bidiTransform(text,"IRNNN","ILNNN") : bidi_const.RLM + bidi_const.RLM + text;
  188. }else{
  189. return bidi_const.LRM + text;
  190. }
  191. }
  192. return text;
  193. }
  194. if(g.isSvgWeb){
  195. if(targetDir == "rtl"){
  196. return bidiEngine.bidiTransform(text,"IRNNN","ILNNN");
  197. }
  198. return text;
  199. }
  200. //unlike the g.Text that is rendered in logical layout for Bidi scripts.
  201. //for g.TextPath in svg always visual -> bidi script is unreadable (except Opera).
  202. if(g.isSvg){
  203. if(has("opera")){
  204. text = bidi_const.LRM + (targetDir == "rtl"? bidi_const.RLE : bidi_const.LRE) + text + bidi_const.PDF;
  205. }else{
  206. text = (targetDir == "rtl") ? bidiEngine.bidiTransform(text,"IRYNN","VLNNN") : bidiEngine.bidiTransform(text,"ILYNN","VLNNN");
  207. }
  208. }
  209. }
  210. return text;
  211. },
  212. bidiPreprocess: function(newText){
  213. if(newText && (typeof newText == "string")){
  214. this.origText = newText;
  215. newText = this.formatText(newText,this.textDir);
  216. }
  217. return newText;
  218. }
  219. });
  220. var extendMethod = function(shape, method, before, after){
  221. // Some helper function. Used for extending metod of shape.
  222. // shape: Object
  223. // The shape we overriding it's metod.
  224. // method: String
  225. // The method that is extended, the original metod is called before or after
  226. // functions that passed to extendMethod.
  227. // before: function
  228. // If defined this function will be executed before the original method.
  229. // after: function
  230. // If defined this function will be executed after the original method.
  231. var old = shape.prototype[method];
  232. shape.prototype[method] =
  233. function(){
  234. var rBefore;
  235. if (before){
  236. rBefore = before.apply(this, arguments);
  237. }
  238. var r = old.call(this, rBefore);
  239. if (after){
  240. r = after.call(this, r, arguments);
  241. }
  242. return r;
  243. };
  244. };
  245. var bidiPreprocess = function(newText){
  246. if (newText){
  247. if (newText.textDir){
  248. newText.textDir = validateTextDir(newText.textDir);
  249. }
  250. if (newText.text && (newText.text instanceof Array)){
  251. newText.text = newText.text.join(",");
  252. }
  253. }
  254. if(newText && (newText.text != undefined || newText.textDir) && (this.textDir != newText.textDir || newText.text != this.origText)){
  255. // store the original text.
  256. this.origText = (newText.text != undefined) ? newText.text : this.origText;
  257. if(newText.textDir){
  258. this.textDir = newText.textDir;
  259. }
  260. newText.text = this.formatText(this.origText,this.textDir);
  261. }
  262. return this.bidiPreprocess(newText);
  263. };
  264. // Istead of adding bidiPreprocess to all renders one by one
  265. // use the extendMethod, at first there's a need for bidi transformation
  266. // on text then call to original setShape.
  267. extendMethod(g.Text,"setShape", bidiPreprocess, null);
  268. extendMethod(g.TextPath,"setText", bidiPreprocess, null);
  269. var restoreText = function(origObj){
  270. var obj = lang.clone(origObj);
  271. if (obj && this.origText){
  272. obj.text = this.origText;
  273. }
  274. return obj;
  275. };
  276. // Istead of adding restoreText to all renders one by one
  277. // use the extendMethod, at first get the shape by calling the original getShape,
  278. // than resrore original text (without the text transformations).
  279. extendMethod(g.Text, "getShape", null, restoreText);
  280. extendMethod(g.TextPath, "getText", null, restoreText);
  281. var groupTextDir = function(group, args){
  282. var textDir;
  283. if (args && args[0]){
  284. textDir = validateTextDir(args[0]);
  285. }
  286. group.setTextDir(textDir ? textDir : this.textDir);
  287. return group; // dojox.gfx.Group
  288. };
  289. // In creation of Group there's a need to update it's textDir,
  290. // so instead of doing it in renders one by one (vml vs others)
  291. // use the extendMethod, at first the original createGroup is applied, the
  292. // groupTextDir which is setts Group's textDir as it's father's or if was defined
  293. // by user by this value.
  294. extendMethod(g.Surface, "createGroup", null, groupTextDir);
  295. extendMethod(g.Group, "createGroup", null, groupTextDir);
  296. var textDirPreprocess = function(text){
  297. // inherit from surface / group if textDir is defined there
  298. if(text){
  299. var textDir = text.textDir ? validateTextDir(text.textDir) : this.textDir;
  300. if(textDir){
  301. text.textDir = textDir;
  302. }
  303. }
  304. return text;
  305. };
  306. // In creation there's a need to some preprocess,
  307. // so instead of doing it in renders one by one (vml vs others)
  308. // use the extendMethod, at first the textDirPreprocess function handles the input
  309. // then the original createXXXXXX is applied.
  310. extendMethod(g.Surface,"createText", textDirPreprocess, null);
  311. extendMethod(g.Surface,"createTextPath", textDirPreprocess, null);
  312. extendMethod(g.Group,"createText", textDirPreprocess, null);
  313. extendMethod(g.Group,"createTextPath", textDirPreprocess, null);
  314. g.createSurface = function(parentNode, width, height, textDir) {
  315. var s = g[g.renderer].createSurface(parentNode, width, height);
  316. var tDir = validateTextDir(textDir);
  317. if(g.isSvgWeb){
  318. s.textDir = tDir ? tDir : html.style(dom.byId(parentNode),"direction");
  319. return s;
  320. }
  321. // if textDir was defined use it, else get default value.
  322. //s.textDir = tDir ? tDir : html.style(s.rawNode,"direction");
  323. if(g.isVml || g.isSvg || g.isCanvas){
  324. s.textDir = tDir ? tDir : html.style(s.rawNode,"direction");
  325. }
  326. if(g.isSilverlight){
  327. // allow this once rawNode will be able for the silverlight
  328. //s.textDir = tDir ? tDir : dojo.style(s.rawNode,"direction");
  329. s.textDir = tDir ? tDir : html.style(s._nodes[1],"direction");
  330. }
  331. return s;
  332. };
  333. // some helper functions
  334. function setTextDir(/*Object*/ obj, /*String*/ newTextDir){
  335. var tDir = validateTextDir(newTextDir);
  336. if (tDir){
  337. g.utils.forEach(obj,function(e){
  338. if(e instanceof g.Surface || e instanceof g.Group){
  339. e.textDir = tDir;
  340. }
  341. if(e instanceof g.Text){
  342. e.setShape({textDir: tDir});
  343. }
  344. if(e instanceof g.TextPath){
  345. e.setText({textDir: tDir})
  346. }
  347. }, obj);
  348. }
  349. return obj;
  350. }
  351. function validateTextDir(textDir){
  352. var validValues = ["ltr","rtl","auto"];
  353. if (textDir){
  354. textDir = textDir.toLowerCase();
  355. if (arr.indexOf(validValues, textDir) < 0){
  356. return null;
  357. }
  358. }
  359. return textDir;
  360. }
  361. return g; // return gfx api augmented with bidi support
  362. });