HtmlShape.cs 20 KB

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