HtmlText.hx 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. package h2d;
  2. import h2d.Text;
  3. class HtmlText extends Text {
  4. var elements : Array<Object> = [];
  5. var xPos : Int;
  6. var yPos : Int;
  7. var xMax : Int;
  8. var xMin : Int;
  9. var sizePos : Int;
  10. var dropMatrix : h3d.shader.ColorMatrix;
  11. var prevChar : Int;
  12. override function draw(ctx:RenderContext) {
  13. if( dropShadow != null ) {
  14. var oldX = absX, oldY = absY;
  15. absX += dropShadow.dx * matA + dropShadow.dy * matC;
  16. absY += dropShadow.dx * matB + dropShadow.dy * matD;
  17. if( dropMatrix == null )
  18. dropMatrix = new h3d.shader.ColorMatrix();
  19. addShader(dropMatrix);
  20. var m = dropMatrix.matrix;
  21. m.zero();
  22. m._41 = ((dropShadow.color >> 16) & 0xFF) / 255;
  23. m._42 = ((dropShadow.color >> 8) & 0xFF) / 255;
  24. m._43 = (dropShadow.color & 0xFF) / 255;
  25. m._44 = dropShadow.alpha;
  26. glyphs.drawWith(ctx, this);
  27. removeShader(dropMatrix);
  28. absX = oldX;
  29. absY = oldY;
  30. } else
  31. dropMatrix = null;
  32. glyphs.drawWith(ctx,this);
  33. }
  34. public dynamic function loadImage( url : String ) : Tile {
  35. return null;
  36. }
  37. public dynamic function loadFont( name : String ) : Font {
  38. return font;
  39. }
  40. override function initGlyphs( text : String, rebuild = true, handleAlign = true, ?lines : Array<Int> ) {
  41. if( rebuild ) {
  42. glyphs.clear();
  43. for( e in elements ) e.remove();
  44. elements = [];
  45. }
  46. glyphs.setDefaultColor(textColor);
  47. xPos = 0;
  48. xMin = 0;
  49. var align = handleAlign ? textAlign : Left;
  50. switch( align ) {
  51. case Center, Right, MultilineCenter, MultilineRight:
  52. lines = [];
  53. initGlyphs(text, false, false, lines);
  54. var max = if( align == MultilineCenter || align == MultilineRight ) calcWidth else realMaxWidth < 0 ? 0 : Std.int(realMaxWidth);
  55. var k = align == Center || align == MultilineCenter ? 1 : 0;
  56. for( i in 0...lines.length )
  57. lines[i] = (max - lines[i]) >> k;
  58. xPos = lines.shift();
  59. xMin = xPos;
  60. default:
  61. }
  62. yPos = 0;
  63. xMax = 0;
  64. sizePos = 0;
  65. calcYMin = 0;
  66. var doc = try Xml.parse(text) catch( e : Dynamic ) throw "Could not parse " + text + " (" + e +")";
  67. var sizes = [];
  68. prevChar = -1;
  69. for( e in doc )
  70. buildSizes(e, sizes);
  71. prevChar = -1;
  72. for( e in doc )
  73. addNode(e, font, rebuild, handleAlign, sizes, lines);
  74. if (!handleAlign && !rebuild && lines != null) lines.push(xPos);
  75. if( xPos > xMax ) xMax = xPos;
  76. var x = xPos, y = yPos;
  77. calcXMin = xMin;
  78. calcWidth = xMax - xMin;
  79. calcHeight = y + font.lineHeight;
  80. calcSizeHeight = y + (font.baseLine > 0 ? font.baseLine : font.lineHeight);
  81. calcDone = true;
  82. }
  83. function buildSizes( e : Xml, sizes : Array<Int> ) {
  84. if( e.nodeType == Xml.Element ) {
  85. var len = 0, prevFont = font;
  86. switch( e.nodeName.toLowerCase() ) {
  87. case "br":
  88. len = -1; // break
  89. case "img":
  90. var i = loadImage(e.get("src"));
  91. len = (i == null ? 8 : i.width) + letterSpacing;
  92. case "font":
  93. for( a in e.attributes() ) {
  94. var v = e.get(a);
  95. switch( a.toLowerCase() ) {
  96. case "face": font = loadFont(v);
  97. default:
  98. }
  99. }
  100. default:
  101. }
  102. sizes.push(len);
  103. for( child in e )
  104. buildSizes(child, sizes);
  105. font = prevFont;
  106. } else {
  107. var text = htmlToText(e.nodeValue);
  108. var xp = 0;
  109. for( i in 0...text.length ) {
  110. var cc = text.charCodeAt(i);
  111. var e = font.getChar(cc);
  112. var sz = e.getKerningOffset(prevChar) + e.width;
  113. if( cc == "\n".code || font.charset.isBreakChar(cc) ) {
  114. if( cc != "\n".code && !font.charset.isSpace(cc) )
  115. xp += sz;
  116. sizes.push( -(xp + 1));
  117. return;
  118. }
  119. xp += sz + letterSpacing;
  120. }
  121. sizes.push(xp);
  122. }
  123. }
  124. static var REG_SPACES = ~/[\r\n\t ]+/g;
  125. static var REG_HTMLENTITIES = ~/&([A-Za-z]+);/g;
  126. function htmlToText( t : hxd.UString ) {
  127. t = REG_SPACES.replace(t, " ");
  128. t = REG_HTMLENTITIES.map(t, function(r) {
  129. switch( r.matched(1).toLowerCase() ) {
  130. case "lt": return "<";
  131. case "gt": return ">";
  132. case "nbsp": return String.fromCharCode(0xA0);
  133. default: return r.matched(0);
  134. }
  135. });
  136. return t;
  137. }
  138. function remainingSize( sizes : Array<Int> ) {
  139. var size = 0;
  140. for( i in sizePos...sizes.length ) {
  141. var s = sizes[i];
  142. if( s < 0 ) {
  143. size += -s - 1;
  144. return size;
  145. }
  146. size += s;
  147. }
  148. return size;
  149. }
  150. function addNode( e : Xml, font : Font, rebuild : Bool, handleAlign:Bool, sizes : Array<Int>, ?lines : Array<Int> = null ) {
  151. sizePos++;
  152. var calcLines = !handleAlign && !rebuild && lines != null;
  153. var align = handleAlign ? textAlign : Left;
  154. if( e.nodeType == Xml.Element ) {
  155. var prevColor = null, prevGlyphs = null;
  156. switch( e.nodeName.toLowerCase() ) {
  157. case "font":
  158. for( a in e.attributes() ) {
  159. var v = e.get(a);
  160. switch( a.toLowerCase() ) {
  161. case "color":
  162. if( prevColor == null ) prevColor = @:privateAccess glyphs.curColor.clone();
  163. if( v.charCodeAt(0) == '#'.code && v.length == 4 )
  164. v = "#" + v.charAt(1) + v.charAt(1) + v.charAt(2) + v.charAt(2) + v.charAt(3) + v.charAt(3);
  165. glyphs.setDefaultColor(Std.parseInt("0x" + v.substr(1)));
  166. case "opacity":
  167. if( prevColor == null ) prevColor = @:privateAccess glyphs.curColor.clone();
  168. @:privateAccess glyphs.curColor.a *= Std.parseFloat(v);
  169. case "face":
  170. font = loadFont(v);
  171. if( prevGlyphs == null ) prevGlyphs = glyphs;
  172. var prev = glyphs;
  173. glyphs = new TileGroup(font == null ? null : font.tile, this);
  174. @:privateAccess glyphs.curColor.load(prev.curColor);
  175. elements.push(glyphs);
  176. default:
  177. }
  178. }
  179. case "br":
  180. if( xPos > xMax ) xMax = xPos;
  181. if( calcLines ) lines.push(xPos);
  182. switch( align ) {
  183. case Left:
  184. xPos = 0;
  185. case Right, Center, MultilineCenter, MultilineRight:
  186. xPos = lines.shift();
  187. if( xPos < xMin ) xMin = xPos;
  188. }
  189. yPos += font.lineHeight + lineSpacing;
  190. prevChar = -1;
  191. case "img":
  192. var i = loadImage(e.get("src"));
  193. if( i == null ) i = Tile.fromColor(0xFF00FF, 8, 8);
  194. if( realMaxWidth >= 0 && xPos + i.width + letterSpacing + remainingSize(sizes) > realMaxWidth && xPos > 0 ) {
  195. if( xPos > xMax ) xMax = xPos;
  196. xPos = 0;
  197. yPos += font.lineHeight + lineSpacing;
  198. }
  199. var py = yPos + font.baseLine - i.height;
  200. if( py + i.dy < calcYMin )
  201. calcYMin = py + i.dy;
  202. if( rebuild ) {
  203. var b = new Bitmap(i, this);
  204. b.x = xPos;
  205. b.y = py;
  206. elements.push(b);
  207. }
  208. xPos += i.width + letterSpacing;
  209. default:
  210. }
  211. for( child in e )
  212. addNode(child, font, rebuild, handleAlign, sizes, lines);
  213. if( prevGlyphs != null )
  214. glyphs = prevGlyphs;
  215. if( prevColor != null )
  216. @:privateAccess glyphs.curColor.load(prevColor);
  217. } else {
  218. var t = splitText(htmlToText(e.nodeValue), xPos, remainingSize(sizes));
  219. var dy = this.font.baseLine - font.baseLine;
  220. for( i in 0...t.length ) {
  221. var cc = t.charCodeAt(i);
  222. var e = font.getChar(cc);
  223. if( cc == "\n".code ) {
  224. if( xPos > xMax ) xMax = xPos;
  225. if( calcLines ) lines.push(xPos);
  226. switch( align ) {
  227. case Left:
  228. xPos = 0;
  229. case Right, Center, MultilineCenter, MultilineRight:
  230. xPos = lines.shift();
  231. if( xPos < xMin ) xMin = xPos;
  232. }
  233. yPos += font.lineHeight + lineSpacing;
  234. prevChar = -1;
  235. continue;
  236. }
  237. else {
  238. if (e != null) {
  239. xPos += e.getKerningOffset(prevChar);
  240. if( rebuild ) glyphs.add(xPos, yPos + dy, e.t);
  241. if( yPos == 0 && e.t.dy+dy < calcYMin ) calcYMin = e.t.dy + dy;
  242. xPos += e.width + letterSpacing;
  243. }
  244. prevChar = cc;
  245. }
  246. }
  247. }
  248. }
  249. override function set_textColor(c) {
  250. if( this.textColor == c ) return c;
  251. this.textColor = c;
  252. rebuild();
  253. return c;
  254. }
  255. override function getBoundsRec( relativeTo : Object, out : h2d.col.Bounds, forSize : Bool ) {
  256. if( forSize )
  257. for( i in elements )
  258. if( Std.is(i,h2d.Bitmap) )
  259. i.visible = false;
  260. super.getBoundsRec(relativeTo, out, forSize);
  261. if( forSize )
  262. for( i in elements )
  263. i.visible = true;
  264. }
  265. }