using DocumentFormat.OpenXml; using DocumentFormat.OpenXml.Drawing; using DocumentFormat.OpenXml.Drawing.Diagrams; using DocumentFormat.OpenXml.Packaging; using DocumentFormat.OpenXml.Presentation; using HTEXLib.Helpers.ShapeHelpers; using HTEXLib.Models.Inner; using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Text; namespace HTEXLib.Models.HTEX { public class HtexTable: HtexElement { public HtexTable(string id, double rot, double width, double height, double top, double left, bool invisible, bool animatable, int index, DocumentFormat.OpenXml.Drawing.Table table, PPTSlide slide, string partForm) { base.slide = slide; this.rot = rot; this.Table = table; base.id = id; base.top = top; base.left = left; base.width = width; base.height = height; base.invisible = invisible; base.animatable = animatable; base.index = index; base.type = "Table"; base.partForm = partForm; } public override List DrawElement() { Position position = new Position { cx = width, cy = height, x = left, y = top, rot = rot }; TbStyle tbStyle = PPTXHelper.DoTableProperties(Table.TableProperties, slide, type, partForm); List columns = Table.TableGrid.Elements().ToList(); tbStyle.ColumnWidth = columns.Select(x => x.Width * Globals.px96 * 1.0 / Globals.px914400).ToList(); List rows = Table.Elements().ToList(); var trs = DoTableRow(rows, slide, type, partForm); Table table = new Table() { id = this.id, tr = trs, style = tbStyle, type =type, index = index, animatable = animatable, invisible = invisible }; table.style.position = position; var elmt = new List(); elmt.Add(table); return elmt; } public DocumentFormat.OpenXml.Drawing.Table Table { get; set; } public List DoTableRow(List rows, PPTSlide slide, string type, string partForm) { List trs = new List(); foreach (TableRow row in rows) { Tr tr = new Tr { height = row.Height * Globals.px96 * 1.0 / Globals.px914400 }; var r = row.Elements(); foreach (TableCell cell in r) { CellStyle cellStyle = PPTXHelper.DoTableCellProperties(cell.TableCellProperties, slide, type, partForm); Td td = new Td(); if (cell != null) { if (cell.RowSpan != null) { td.rowspan = cell.RowSpan; } if (cell.GridSpan != null) { td.colspan = cell.GridSpan; } if (cell.HorizontalMerge != null) { td.hmerge = cell.HorizontalMerge; } if (cell.VerticalMerge != null) { td.vmerge = cell.VerticalMerge; } } td.style = cellStyle; LinkedList PPTParagraphs = DoTextBody(cell.TextBody, slide.SlidePart, slide.shapeListStyleMaster, slide.shapeListStyleLayout); List paragraphs = DrawText(PPTParagraphs); td.paragraphs = paragraphs; tr.td.Add(td); } trs.Add(tr); } return trs; } public List DrawText(LinkedList Texts) { List Paragraphs = new List(); foreach (var par in Texts) { double paragraphTop = par.getSpaceBeforePoints(); Paragraph paragraph = new HTEXLib.Paragraph { animatable = par.Animatable, invisible = par.Invisible }; paragraph.style.hori = par.Align; if (par.bullet != null) { var bullet = par.bullet; paragraph.buChar = new BuChar { type = bullet.BulletType, left = bullet.Left, buchar = bullet.Text, color = bullet.FontColor, typeface = bullet.FontFamily, size = bullet.FontSize, }; } int newTop = par.getSpaceBeforePoints(); int left = 0; List textElements = new List(); List texts = new List(); if (par.RunPropList == null || par.RunPropList.Count == 0 && par.defaultRunProperties != null) //Only paragraph! { float points = float.Parse(par.defaultRunProperties.FontSize.ToString()) * 72.0F / 96.0F; System.Drawing.Font font = new System.Drawing.Font(par.defaultRunProperties.FontFamily.ToString(), points); newTop = font.Height; } List processedElements = new List(); IEnumerable pPTRunProperties = breakTextsToShape(par); foreach (var text in pPTRunProperties) { float points = float.Parse(text.FontSize.ToString()) * 72.0F / 96.0F; System.Drawing.Font font = new System.Drawing.Font(text.FontFamily.ToString(), points); if (text.Bold) font = new System.Drawing.Font(text.FontFamily.ToString(), points, System.Drawing.FontStyle.Bold); else if (text.Italic) font = new System.Drawing.Font(text.FontFamily.ToString(), points, System.Drawing.FontStyle.Italic); else if (text.Underline != null && text.Underline.Equals("Single")) font = new System.Drawing.Font(text.FontFamily.ToString(), points, System.Drawing.FontStyle.Underline); newTop = font.Height > newTop ? font.Height : newTop; newTop = par.getLineSpacingInPointsFromFont(newTop); if (text.isBreak) { top += newTop; left = 0; fixLeftSpacingForAlignment(processedElements, par, font); processedElements.Clear(); continue; } String currentString = text.Text.TrimEnd() + getStringFromTextElements(processedElements); HtexText t1 = new HtexText(left: left, top: top, fontFamily: text.FontFamily, fontColor: text.FontColor, fontSize: text.FontSize, isBullet: text.isBullet, bold: text.Bold, italic: text.Italic, underline: text.Underline, id: id, slideIndex: slide.slideIndex) { Rotate = this.rot }; t1.width = MeasureString(text.Text, font); if (text.isBullet && text.Text != null && text.Text.Contains("rId")) { t1.PictureBullet = true; t1.width = text.bulletSize; t1.bulletSize = text.bulletSize; newTop = text.bulletSize; } t1.Text = text.Text; textElements.Add(t1); texts.Add(new Text { content = t1.Text, link = text.link, linkType = text.linkType, style = new FontStyle { align = par.FontAlign, spacing = par.defTabSize, color = text.FontColor, family = text.FontFamily, top = top, left = left, size = text.FontSize, isBullet = text.isBullet, underline = t1.underline, italic = text.Italic, bold = text.Bold, rot = this.rot, width = t1.width, pictureBullet = t1.PictureBullet, bulletSize = t1.bulletSize } }); processedElements.Add(t1); } fixLeftSpacingForAlignment(processedElements, par); HtexText lastTxt = null; List mergedTextElements = new List(); foreach (HtexText textElement in textElements) { if (lastTxt == null || !lastTxt.sameProps(textElement)) mergedTextElements.Add(textElement); else mergedTextElements[mergedTextElements.Count - 1].Text += textElement.Text; lastTxt = textElement; } //foreach (HtexText textElement in mergedTextElements) { // shapeBuilder.Append(textElement.DrawElement()); //} top += newTop; top += par.getSpaceAfterPoints(newTop); top += par.getSpaceBeforePoints(newTop); paragraph.texts = texts; Paragraphs.Add(paragraph); } return Paragraphs; } private String getStringFromTextElements(List elements) { if (elements == null || elements.Count == 0) return ""; StringBuilder result = new StringBuilder(); foreach (HtexText el in elements) { result.Append(el.Text); } return result.ToString(); } //We have two similar methods - this one is better because it measures the whole string with the font. private void fixLeftSpacingForAlignment(List textElements, PPTParagraph par, System.Drawing.Font font) { int combinedWidth = 0; StringBuilder combinedText = new StringBuilder(); foreach (HtexText textElement in textElements) { if (textElement.PictureBullet) combinedWidth += textElement.bulletSize; else combinedText.Append(textElement.Text); } int bulletOffset = 0; if (par.bullet != null && textElements.Count > 0 && !textElements[0].isBullet) { bulletOffset = par.bullet.bulletSize; combinedWidth += par.bullet.bulletSize; } combinedWidth += MeasureString(combinedText.ToString(), font); double firstLeft = 0; if ("Center".Equals(par.Align)) firstLeft = ((this.width - par.Indent - bulletOffset - par.marginLeft - par.marginRight) - combinedWidth) / 2; else if ("Right".Equals(par.Align)) firstLeft = (this.width - par.Indent - bulletOffset - par.marginLeft - par.marginRight) - combinedWidth; combinedText = new StringBuilder(); combinedWidth = 0; //Now used only for picture bullets! foreach (HtexText textElement in textElements) { textElement.setLeft(firstLeft + par.Indent + bulletOffset + par.marginLeft + combinedWidth + MeasureString(combinedText.ToString(), font)); if (textElement.PictureBullet) combinedWidth += textElement.bulletSize; else combinedText.Append(textElement.Text); } } //We have two similar methods. This is worse because it uses the Width property of each element instead of measuring the whole string. //There is mistake in the calculations coming from that and the difference is bigger when there are more elements. private void fixLeftSpacingForAlignment(List textElements, PPTParagraph par) { int combinedWidth = 0; foreach (HtexText textElement in textElements) combinedWidth += textElement.width; int bulletOffset = 0; if (par.bullet != null && textElements.Count > 0 && !textElements[0].isBullet) { combinedWidth += par.bullet.bulletSize; bulletOffset = par.bullet.bulletSize; } double currentLeft = 0; if ("Center".Equals(par.Align)) currentLeft = ((this.width - par.Indent - bulletOffset - par.marginLeft - par.marginRight) - combinedWidth) / 2; else if ("Right".Equals(par.Align)) currentLeft = (this.width - par.Indent - bulletOffset - par.marginLeft - par.marginRight) - combinedWidth; foreach (HtexText textElement in textElements) { textElement.setLeft(currentLeft + par.Indent + bulletOffset + par.marginLeft); currentLeft += textElement.width; } } private IEnumerable breakTextsToShape(PPTParagraph par) { List list = par.RunPropList; List result = new List(); String previousToken = null; int bulletSize = 0; foreach (var text in list) { float points = float.Parse(text.FontSize.ToString()) * 72.0F / 96.0F; System.Drawing.Font font = new System.Drawing.Font(text.FontFamily.ToString(), points); if (text.Bold) font = new System.Drawing.Font(text.FontFamily.ToString(), points, System.Drawing.FontStyle.Bold); else if (text.Italic) font = new System.Drawing.Font(text.FontFamily.ToString(), points, System.Drawing.FontStyle.Italic); else if (text.Underline != null && text.Underline.Equals("Single")) font = new System.Drawing.Font(text.FontFamily.ToString(), points, System.Drawing.FontStyle.Underline); int size = 0; if (text.isBullet && text.Text != null && text.Text.Contains("rId")) bulletSize = text.bulletSize; else size = MeasureString((previousToken == null ? "" : previousToken + " ") + text.Text, font); if (text.isBreak || size + bulletSize < this.width - par.Indent - par.marginLeft - par.marginRight)//- Shape.LeftInset - Shape.RightInset) { if (text.Text != null && text.Text.Trim() != "" && !(text.isBullet && text.Text.Contains("rId"))) { previousToken = (previousToken == null ? "" : previousToken) + text.Text; } if (text.isBreak) previousToken = null; result.Add(text); continue; } // previousToken = null; string[] tokens = text.Text.Split(' '); int index = 0; foreach (string token in tokens) { index++; int combinedSize = MeasureString((previousToken == null ? "" : previousToken + " ") + token, font); if (combinedSize + bulletSize > this.width - par.Indent - par.marginLeft - par.marginRight)//- Shape.LeftInset - Shape.RightInset) { PPTRunProperties temp = new PPTRunProperties(text); temp.Text = ""; temp.isBreak = true; result.Add(temp); temp = new PPTRunProperties(text); temp.Text = index < tokens.Length ? token + " " : token; result.Add(temp); previousToken = token; } else { PPTRunProperties temp = new PPTRunProperties(text); temp.Text = index < tokens.Length ? token + " " : token; result.Add(temp); previousToken = (previousToken == null ? "" : previousToken + " ") + token; } } } return result; } public static int MeasureString(string s, System.Drawing.Font font) { s = s.Replace("\t", "aaaa");//TODO the replace is dirty hack for measuring tabulations StringFormat stringFormat = new StringFormat(StringFormat.GenericTypographic); CharacterRange[] rng = { new CharacterRange(0, s.Length) }; stringFormat.SetMeasurableCharacterRanges(rng); Graphics g = Graphics.FromImage(new Bitmap(100, 100)); //Use measure character ranges with a big box because we used this for measurement only //Later we might better use this for drawing the text. Region[] regions = g.MeasureCharacterRanges(s, font, new System.Drawing.Rectangle(0, 0, 10000, 3000), stringFormat); foreach (Region region in regions) { RectangleF rect = region.GetBounds(g); return (int)System.Math.Round(rect.Width); } return 0; // // SizeF result; // using (var image = new Bitmap(1, 1)) // { // using (var g = Graphics.FromImage(image)) // { // result = g.MeasureString(s, font); // } // } // // return result.ToSize(); } public LinkedList DoTextBody(DocumentFormat.OpenXml.Drawing.TextBody TextBody, OpenXmlPart slidePart, ListStyle shapeListStyleMaster, ListStyle shapeListStyleLayout) { LinkedList Texts = new LinkedList(); bool IsText = true; int fontScale = 100000; if (TextBody == null) { IsText = false; return null; } if (TextBody.BodyProperties != null) { if (TextBody.BodyProperties.Anchor != null) { DocumentFormat.OpenXml.Drawing.TextAnchoringTypeValues VerticalAlign = TextBody.BodyProperties.Anchor; } if (TextBody.BodyProperties.GetFirstChild() != null && TextBody.BodyProperties.GetFirstChild().FontScale != null) { fontScale = TextBody.BodyProperties.GetFirstChild().FontScale.Value; } } int index = 0; foreach (var paragraph in TextBody.Descendants()) { PlaceholderShape placeholder = null; var par = new PPTParagraph(slide, placeholder,partForm) { Paragraph = index++ }; if (paragraph.ParagraphProperties != null) { int level = paragraph.ParagraphProperties.Level == null ? -1 : paragraph.ParagraphProperties.Level.Value; par.Level = level; } par.SetParagraphProperties(paragraph, slidePart, shapeListStyleMaster, shapeListStyleLayout); bool hasText = false; foreach (var obj in paragraph.ChildElements) { hasText = GetParagraphChildElements(TextBody, par, hasText, obj, fontScale); } //This is because when we set paragraph properties we add the bullet to the text runs. //If we don't have text it still outputs the bullet character. if (par.bullet != null && hasText) { par.RunPropList.Insert(0, par.bullet); } Texts.AddLast(par); } return Texts; } private bool GetParagraphChildElements(DocumentFormat.OpenXml.Drawing.TextBody shape, PPTParagraph par, bool hasText, OpenXmlElement obj, int fontScale) { LinkedList effectShapes = new LinkedList(); if (obj is Run) { Run run = (Run)obj; hasText = true; PPTRunProperties runProp = new PPTRunProperties(par.defaultRunProperties); runProp.Text = run.Text.Text; runProp.SetRunProperties(run.RunProperties, shape, ref effectShapes); runProp.FontSize = System.Math.Round(fontScale * runProp.FontSize / Globals.PercentageConstant); par.RunPropList.Add(runProp); } else if (obj is Field) { Field run = (Field)obj; hasText = true; PPTRunProperties runProp = new PPTRunProperties(par.defaultRunProperties); runProp.Text = run.Text.Text; runProp.SetRunProperties(run.RunProperties, shape, ref effectShapes); runProp.FontSize = System.Math.Round(fontScale * runProp.FontSize / Globals.PercentageConstant); par.RunPropList.Add(runProp); } else if (obj is Break) { Break aBreak = (Break)obj; PPTRunProperties runProp = new PPTRunProperties(par.defaultRunProperties); runProp.SetRunProperties(aBreak.RunProperties, shape, ref effectShapes); runProp.FontSize = System.Math.Round(fontScale * runProp.FontSize / Globals.PercentageConstant); runProp.isBreak = true; par.RunPropList.Add(runProp); } return hasText; } } }