HtmlShape.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. using System;
  2. using System.Text;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using ClearSlideLibrary.Dom;
  6. using System.Drawing;
  7. using ClearSlideLibrary.Dom.PPTTexts;
  8. namespace ClearSlideLibrary.HtmlController
  9. {
  10. internal class HtmlShape : HtmlPresentationElement
  11. {
  12. public PPTShape Shape { get; set; }
  13. public double Rotate { get; set; }
  14. public string HyperLink { get; set; }
  15. private int slideIndex;
  16. public HtmlShape(string id, int width, int height,
  17. int top, int left, bool invisible,
  18. bool animatable, int slideIndex)
  19. {
  20. base.id = id;
  21. base.top = top;
  22. base.left = left;
  23. base.width = width;
  24. base.height = height;
  25. base.invisible = invisible;
  26. base.animatable = animatable;
  27. this.slideIndex = slideIndex;
  28. }
  29. public override string DrawElement()
  30. {
  31. StringBuilder shapeBuilder = new StringBuilder();
  32. //we need this for text (if any in the shape).
  33. /*string slideId = id.Substring(0, 2);
  34. int slideNumber = Int32.Parse(slideId.Substring(1, 1));
  35. string shapeId = id.Substring(2, 2);
  36. int shapeNumber = Int32.Parse(shapeId.Substring(1, 1));*/
  37. string style = invisible ? "DC0" : "DC1";
  38. //the object has animation.
  39. if (animatable)
  40. {
  41. shapeBuilder.Append("<div id=\"" + id + "\" style=\"top:" + top.ToString() + "px;left:" + left.ToString() +
  42. "px;height:" + height.ToString() + "px;width:" + width.ToString() + "px;\">");
  43. shapeBuilder.Append("<div class=\"" + style + "\" id=\"" + id + "c" + "\">");
  44. shapeBuilder.Append("<img />");
  45. shapeBuilder.Append("</div>");
  46. shapeBuilder.Append("</div>");
  47. if (Shape.IsText)
  48. {
  49. if (Shape.Texts != null)
  50. {
  51. DrawText(shapeBuilder);
  52. }
  53. }
  54. }
  55. else
  56. {
  57. shapeBuilder.Append("<div id=\"" + id + "\" style=\"top:" + top.ToString() + "px;left:" + left.ToString() +
  58. "px;height:" + height.ToString() + "px;width:" + width.ToString() + "px;\">");
  59. shapeBuilder.Append("<img/>");
  60. shapeBuilder.Append("</div>");
  61. if (Shape.IsText)
  62. {
  63. if (Shape.Texts != null)
  64. {
  65. DrawText(shapeBuilder);
  66. }
  67. }
  68. }
  69. return shapeBuilder.ToString();
  70. }
  71. private void DrawText(StringBuilder shapeBuilder)
  72. {
  73. int top = getTopForCurrentAnchoring(this.Shape.VerticalAlign, this.Shape.Texts);
  74. foreach (var par in Shape.Texts)
  75. {
  76. //If top add spacing before - otherwise not (I think)
  77. int paragraphTop = this.top + (this.Shape.VerticalAlign.Equals(DocumentFormat.OpenXml.Drawing.TextAnchoringTypeValues.Top) ? par.getSpaceBeforePoints() : 0);
  78. string htmlStyle = par.Invisible ? "DC0" : "DC1";
  79. if (par.Animatable)
  80. {
  81. shapeBuilder.Append("<div id=\"" + id + "p" + par.Paragraph + "\" style=\"top:" + paragraphTop + "px;left:" + this.left.ToString() +
  82. "px;height:" + height.ToString() + "px;width:" + width.ToString() + "px;\">");
  83. shapeBuilder.Append("<div class=\"" + htmlStyle + "\" id=\"" + id + "p" + par.Paragraph + "c" + "\">");
  84. }
  85. else
  86. {
  87. shapeBuilder.Append("<div id=\"" + id + "p" + par.Paragraph + "\" style=\"top:" + (this.top).ToString() + "px;left:" + this.left.ToString() +
  88. "px;height:" + height.ToString() + "px;width:" + width.ToString() + "px;\">");
  89. }
  90. int newTop = par.getSpaceBeforePoints();
  91. int left = 0;
  92. List<HtmlText> textElements = new List<HtmlText>();
  93. if (par.RunPropList == null || par.RunPropList.Count == 0 && par.defaultRunProperties != null) //Only paragraph!
  94. {
  95. float points = float.Parse(par.defaultRunProperties.FontSize.ToString()) * 72.0F / 96.0F;
  96. Font font = new System.Drawing.Font(par.defaultRunProperties.FontFamily.ToString(), points);
  97. newTop = font.Height;
  98. }
  99. List<HtmlText> processedElements = new List<HtmlText>();
  100. foreach (var text in breakTextsToShape(par))
  101. {
  102. float points = float.Parse(text.FontSize.ToString()) * 72.0F / 96.0F;
  103. Font font = new System.Drawing.Font(text.FontFamily.ToString(), points);
  104. if (text.Bold) font = new System.Drawing.Font(text.FontFamily.ToString(), points, FontStyle.Bold);
  105. else if (text.Italic)
  106. font = new System.Drawing.Font(text.FontFamily.ToString(), points, FontStyle.Italic);
  107. else if (text.Underline != null && text.Underline.Equals("Single"))
  108. font = new System.Drawing.Font(text.FontFamily.ToString(), points, FontStyle.Underline);
  109. newTop = font.Height > newTop ? font.Height : newTop;
  110. newTop = par.getLineSpacingInPointsFromFont(newTop);
  111. if (text.isBreak)
  112. {
  113. top += newTop;
  114. left = 0;
  115. fixLeftSpacingForAlignment(processedElements, par,font);
  116. processedElements.Clear();
  117. continue;
  118. }
  119. String currentString = text.Text.TrimEnd()+ getStringFromTextElements(processedElements);
  120. //Text must already be broken to lines
  121. //Size size = MeasureString(currentString, font);
  122. // if (size.Width > this.width - par.Indent - par.marginLeft - par.marginRight)
  123. // {
  124. // top += newTop;
  125. // left = 0;
  126. // fixLeftSpacingForAlignment(processedElements, par, font);
  127. // processedElements.Clear();
  128. // }
  129. HtmlText t1 = new HtmlText(left: left,
  130. top: top,
  131. fontFamily: text.FontFamily,
  132. fontColor: text.FontColor,
  133. fontSize: text.FontSize,
  134. isBullet: text.isBullet,
  135. bold: text.Bold,
  136. italic: text.Italic,
  137. underline: text.Underline,
  138. id: id,
  139. slideIndex: slideIndex)
  140. {
  141. Rotate = Rotate
  142. };
  143. t1.width = MeasureString(text.Text, font);
  144. if (text.isBullet && text.Text != null && text.Text.Contains("rId"))
  145. {
  146. t1.PictureBullet = true;
  147. t1.width = text.bulletSize;
  148. t1.bulletSize = text.bulletSize;
  149. newTop = text.bulletSize;
  150. }
  151. t1.Text = text.Text;
  152. textElements.Add(t1);
  153. processedElements.Add(t1);
  154. }
  155. fixLeftSpacingForAlignment(processedElements, par);
  156. HtmlText lastTxt = null;
  157. List<HtmlText> mergedTextElements = new List<HtmlText>();
  158. foreach (HtmlText textElement in textElements)
  159. {
  160. if (lastTxt == null || !lastTxt.sameProps(textElement))
  161. mergedTextElements.Add(textElement);
  162. else
  163. mergedTextElements[mergedTextElements.Count - 1].Text += textElement.Text;
  164. lastTxt = textElement;
  165. }
  166. foreach (HtmlText textElement in mergedTextElements)
  167. shapeBuilder.Append(textElement.DrawElement());
  168. top += newTop;
  169. top += par.getSpaceAfterPoints(newTop);
  170. top += par.getSpaceBeforePoints(newTop);
  171. shapeBuilder.Append("</div>");
  172. if (par.Animatable)
  173. shapeBuilder.Append("</div>");
  174. }
  175. }
  176. private String getStringFromTextElements(List<HtmlText> elements)
  177. {
  178. if (elements == null || elements.Count == 0)
  179. return "";
  180. StringBuilder result=new StringBuilder();
  181. foreach(HtmlText el in elements){
  182. result.Append(el.Text);
  183. }
  184. return result.ToString();
  185. }
  186. //We have two similar methods. This is worse because it uses the Width property of each element instead of measuring the whole string.
  187. //There is mistake in the calculations coming from that and the difference is bigger when there are more elements.
  188. private void fixLeftSpacingForAlignment(List<HtmlText> textElements, PPTParagraph par)
  189. {
  190. int combinedWidth = 0;
  191. foreach (HtmlText textElement in textElements)
  192. combinedWidth += textElement.width;
  193. int bulletOffset = 0;
  194. if (par.bullet != null && textElements.Count > 0 && !textElements[0].isBullet)
  195. {
  196. combinedWidth += par.bullet.bulletSize;
  197. bulletOffset = par.bullet.bulletSize;
  198. }
  199. int currentLeft = 0;
  200. if ("Center".Equals(par.Align))
  201. currentLeft = ((this.width - par.Indent - bulletOffset - par.marginLeft - par.marginRight - Shape.LeftInset - Shape.RightInset) - combinedWidth) / 2;
  202. else if ("Right".Equals(par.Align))
  203. currentLeft = (this.width - par.Indent - bulletOffset - par.marginLeft - par.marginRight - Shape.LeftInset - Shape.RightInset) - combinedWidth;
  204. foreach (HtmlText textElement in textElements)
  205. {
  206. textElement.setLeft(currentLeft + par.Indent + bulletOffset + par.marginLeft + Shape.LeftInset);
  207. currentLeft += textElement.width;
  208. }
  209. }
  210. //We have two similar methods - this one is better because it measures the whole string with the font.
  211. private void fixLeftSpacingForAlignment(List<HtmlText> textElements, PPTParagraph par, Font font)
  212. {
  213. int combinedWidth = 0;
  214. StringBuilder combinedText = new StringBuilder();
  215. foreach (HtmlText textElement in textElements)
  216. {
  217. if(textElement.PictureBullet)
  218. combinedWidth += textElement.bulletSize;
  219. else
  220. combinedText.Append(textElement.Text);
  221. }
  222. int bulletOffset = 0;
  223. if (par.bullet != null && textElements.Count > 0 && !textElements[0].isBullet)
  224. {
  225. bulletOffset = par.bullet.bulletSize;
  226. combinedWidth += par.bullet.bulletSize;
  227. }
  228. combinedWidth += MeasureString(combinedText.ToString(), font);
  229. int firstLeft = 0;
  230. if ("Center".Equals(par.Align))
  231. firstLeft = ((this.width - par.Indent - bulletOffset - par.marginLeft - par.marginRight - Shape.LeftInset - Shape.RightInset) - combinedWidth) / 2;
  232. else if ("Right".Equals(par.Align))
  233. firstLeft = (this.width - par.Indent - bulletOffset - par.marginLeft - par.marginRight - Shape.LeftInset - Shape.RightInset) - combinedWidth;
  234. combinedText = new StringBuilder();
  235. combinedWidth = 0; //Now used only for picture bullets!
  236. foreach (HtmlText textElement in textElements)
  237. {
  238. textElement.setLeft(firstLeft + par.Indent + bulletOffset + par.marginLeft + combinedWidth + Shape.LeftInset + MeasureString(combinedText.ToString(), font));
  239. if (textElement.PictureBullet)
  240. combinedWidth += textElement.bulletSize;
  241. else
  242. combinedText.Append(textElement.Text);
  243. }
  244. }
  245. private int getTopForCurrentAnchoring(DocumentFormat.OpenXml.Drawing.TextAnchoringTypeValues anchoring, LinkedList<PPTParagraph> paragraphList)
  246. {
  247. if (anchoring.Equals(DocumentFormat.OpenXml.Drawing.TextAnchoringTypeValues.Top))
  248. return Shape.TopInset;
  249. int combinedHeight = 0;
  250. foreach (PPTParagraph par in paragraphList)
  251. {
  252. int newTop = 0;
  253. IEnumerable<PPTRunProperties> splitText=breakTextsToShape(par);
  254. foreach (var text in splitText)
  255. {
  256. float points = float.Parse(text.FontSize.ToString()) * 72.0F / 96.0F;
  257. Font font = new System.Drawing.Font(text.FontFamily.ToString(), points);
  258. if (text.Bold) font = new System.Drawing.Font(text.FontFamily.ToString(), points, FontStyle.Bold);
  259. else if (text.Italic) font = new System.Drawing.Font(text.FontFamily.ToString(), points, FontStyle.Italic);
  260. else if (text.Underline != null && text.Underline.Equals("Single"))
  261. font = new System.Drawing.Font(text.FontFamily.ToString(), points, FontStyle.Underline);
  262. if (text.isBreak)
  263. {
  264. combinedHeight += newTop;
  265. newTop = font.Height;
  266. continue;
  267. }
  268. newTop = font.Height > newTop ? font.Height : newTop;
  269. }
  270. combinedHeight += par.getLineSpacingInPointsFromFont(newTop);
  271. combinedHeight += par.getSpaceBeforePoints(newTop);
  272. combinedHeight += par.getSpaceAfterPoints(newTop);
  273. }
  274. combinedHeight += Shape.TopInset + Shape.BottomInset;
  275. if (anchoring.Equals(DocumentFormat.OpenXml.Drawing.TextAnchoringTypeValues.Bottom))
  276. return this.height - combinedHeight;
  277. return (this.height - combinedHeight) / 2; //Center align
  278. }
  279. private IEnumerable<PPTRunProperties> breakTextsToShape(PPTParagraph par)
  280. {
  281. List<Dom.PPTTexts.PPTRunProperties> list = par.RunPropList;
  282. List<PPTRunProperties> result = new List<PPTRunProperties>();
  283. String previousToken = null;
  284. int bulletSize = 0;
  285. foreach (var text in list)
  286. {
  287. float points = float.Parse(text.FontSize.ToString()) * 72.0F / 96.0F;
  288. Font font = new System.Drawing.Font(text.FontFamily.ToString(), points);
  289. if (text.Bold) font = new System.Drawing.Font(text.FontFamily.ToString(), points, FontStyle.Bold);
  290. else if (text.Italic) font = new System.Drawing.Font(text.FontFamily.ToString(), points, FontStyle.Italic);
  291. else if (text.Underline != null && text.Underline.Equals("Single"))
  292. font = new System.Drawing.Font(text.FontFamily.ToString(), points, FontStyle.Underline);
  293. int size = 0;
  294. if (text.isBullet && text.Text != null && text.Text.Contains("rId"))
  295. bulletSize = text.bulletSize;
  296. else
  297. size = MeasureString((previousToken == null ? "" : previousToken + " ") + text.Text, font);
  298. if (text.isBreak || size + bulletSize < this.width - par.Indent -par.marginLeft - par.marginRight - Shape.LeftInset - Shape.RightInset)
  299. {
  300. if (text.Text != null && text.Text.Trim() != "" && !(text.isBullet && text.Text.Contains("rId")))
  301. {
  302. previousToken = (previousToken == null ? "" : previousToken ) + text.Text;
  303. }
  304. if (text.isBreak)
  305. previousToken = null;
  306. result.Add(text);
  307. continue;
  308. }
  309. // previousToken = null;
  310. string[] tokens= text.Text.Split(' ');
  311. int index = 0;
  312. foreach (string token in tokens)
  313. {
  314. index++;
  315. int combinedSize = MeasureString((previousToken == null ? "" : previousToken + " ")+token , font);
  316. if (combinedSize + bulletSize > this.width - par.Indent - par.marginLeft - par.marginRight - Shape.LeftInset - Shape.RightInset)
  317. {
  318. PPTRunProperties temp = new PPTRunProperties(text);
  319. temp.Text = "";
  320. temp.isBreak = true;
  321. result.Add(temp);
  322. temp=new PPTRunProperties(text);
  323. temp.Text=index<tokens.Length? token+" ":token;
  324. result.Add(temp);
  325. previousToken = token;
  326. }
  327. else
  328. {
  329. PPTRunProperties temp = new PPTRunProperties(text);
  330. temp.Text = index < tokens.Length ? token + " " : token;
  331. result.Add(temp);
  332. previousToken = (previousToken == null ? "" : previousToken + " ") + token;
  333. }
  334. }
  335. }
  336. return result;
  337. }
  338. public static int MeasureString(string s, Font font)
  339. {
  340. s = s.Replace("\t", "aaaa");//TODO the replace is dirty hack for measuring tabulations
  341. StringFormat stringFormat = new StringFormat(StringFormat.GenericTypographic);
  342. CharacterRange[] rng = { new CharacterRange(0, s.Length) };
  343. stringFormat.SetMeasurableCharacterRanges(rng);
  344. Graphics g= Graphics.FromImage(new Bitmap(100, 100));
  345. //Use measure character ranges with a big box because we used this for measurement only
  346. //Later we might better use this for drawing the text.
  347. Region[] regions =g.MeasureCharacterRanges(s, font, new Rectangle(0, 0, 10000,3000), stringFormat);
  348. foreach (Region region in regions)
  349. {
  350. RectangleF rect = region.GetBounds(g);
  351. return (int)Math.Round(rect.Width);
  352. }
  353. return 0;
  354. //
  355. // SizeF result;
  356. // using (var image = new Bitmap(1, 1))
  357. // {
  358. // using (var g = Graphics.FromImage(image))
  359. // {
  360. // result = g.MeasureString(s, font);
  361. // }
  362. // }
  363. //
  364. // return result.ToSize();
  365. }
  366. public override string ToString()
  367. {
  368. Console.WriteLine("The top is:" + top);
  369. Console.WriteLine("The left is:" + left);
  370. Console.WriteLine("The width is:" + width);
  371. Console.WriteLine("The height is:" + height);
  372. return string.Format("[{0}, {1}, {2}, {3}]", top, left, width, height);
  373. }
  374. }
  375. }