using DocumentFormat.OpenXml.Drawing; 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 HtexShape : HtexElement { public PPTShape Shape { get; set; } // public double Rotate { get; set; } public string HyperLink { get; set; } private int slideIndex; public HtexShape(string id, double rot, double width, double height, double top, double left, bool invisible, bool animatable, int slideIndex, int index, PPTShape shape, PPTSlide slide, string partForm) { base.slide = slide; this.rot = rot; this.Shape = shape; base.id = id; base.top = top; base.left = left; base.width = width; base.height = height; base.invisible = invisible; base.animatable = animatable; this.slideIndex = slideIndex; base.index = index; base.type = "Sp"; base.partForm = partForm; } public override List DrawElement() { if (Shape.placeholder != null && partForm== "layout") { return null; } // string style = invisible ? "DC0" : "DC1"; Position position = new Position { cx= width, cy= height, x=left,y=top,rot=rot}; var ShapeStyle =PPTXHelper.DoShapeProperties(Shape.element.ShapeProperties, slide,type,partForm); //position = position ,mediaType = "image",type = type,index = index,animatable = animatable ,invisible = invisible Shape shape = new Shape() {id=this.id, paragraph = DrawText() ,type=type,index=index,animatable=animatable,invisible=invisible }; shape.style.position = position; var shapeTypeNode = Shape.element.ShapeProperties.GetFirstChild(); var shapeTypeCustom = Shape.element.ShapeProperties.GetFirstChild(); SlideColor slideColor = PPTXHelper.DoShapeStyle(Shape.element.ShapeStyle, slide,type); //从ShapeProperties 获取 p:spPr //再从 ShapeStyle 获取 p:spPr if (ShapeStyle.border == null || ShapeStyle.border.color == null || ShapeStyle.border.color.type == -1) { if (slideColor != null) { shape.style.border.color.solidFill = slideColor.LineColor; shape.style.border.color.type = 2; } } else { shape.style.border = ShapeStyle.border; } if (ShapeStyle.fill == null || ShapeStyle.fill.type == -1) { if (slideColor != null) { shape.style.fill.solidFill = slideColor.FillColor; shape.style.fill.type = 2; } } else { shape.style.fill = ShapeStyle.fill; } if (shape.paragraph != null) { shape.paragraph.ForEach(x => { if (x.texts != null) { x.texts.ForEach(y => { if (y.style.color == null) { if (slideColor != null && slideColor.FontColor != null) { y.style.color = slideColor.FontColor; } else { y.style.color = "#000000"; } } }); } }); } if (shapeTypeNode != null && shapeTypeNode.Preset!=null && !string.IsNullOrEmpty(shapeTypeNode.Preset.InnerText)) { var shapeType = shapeTypeNode.Preset.InnerText; Svg svg = PPTXSvg.GenShapeSvg(Shape.element, index, shapeType, position, shape.style.border); shape.svg = svg; shape.shapeType = shapeType; } if (shapeTypeCustom!=null) { var pathlist= shapeTypeCustom.GetFirstChild(); var path= pathlist.GetFirstChild(); var pathChildren = path.ChildElements; if (pathChildren != null) { var start= pathChildren.Where(x =>x is MoveTo).FirstOrDefault(); var close= pathChildren.Where(x => x is CloseShapePath).FirstOrDefault(); var spX = 96.0; var spY = 96.0; if (start != null) { var point = start.GetFirstChild(); spX = System.Math.Round(double.Parse(point.X.Value )* Globals.px96 *1.0/ Globals.px914400, Globals. degree); spY = System.Math.Round(double.Parse(point.Y.Value) * Globals.px96 * 1.0 / Globals.px914400, Globals.degree); } var d = "M" + spX + "," + spY; foreach (var child in pathChildren) { if (child is LineTo lineTo) { var elm= lineTo.GetFirstChild(); if (elm != null) { var Lx = System.Math.Round(double.Parse(elm.X.Value) * Globals.px96 * 1.0 / Globals.px914400, Globals.degree); var Ly = System.Math.Round(double.Parse(elm.Y.Value) * Globals.px96 * 1.0 / Globals.px914400, Globals.degree); d += " L" + Lx + "," + Ly; } } if (child is ArcTo arcTo) { var WidthRadius = arcTo.WidthRadius; var HeightRadius = arcTo.HeightRadius; var StartAngle = arcTo.StartAngle; var SwingAngle = arcTo.SwingAngle; var wR= System.Math.Round(double.Parse(WidthRadius.Value) * Globals.px96 * 1.0 / Globals.px914400, Globals.degree); var hR = System.Math.Round(double.Parse(HeightRadius.Value) * Globals.px96 * 1.0 / Globals.px914400, Globals.degree); var stAng = System.Math.Round(double.Parse(StartAngle.Value) * Globals.px96 * 1.0 / Globals.px914400, Globals.degree); var swAng = System.Math.Round(double.Parse(SwingAngle.Value) * Globals.px96 * 1.0 / Globals.px914400, Globals.degree); var endAng = stAng + swAng; d +=PPTXSvg .ShapeArc(wR, hR, wR, hR, stAng, endAng, false); } if (child is QuadraticBezierCurveTo quadraticBezierCurveTo) { var elms= quadraticBezierCurveTo.Elements(); if (elms != null) { var list = elms.ToList(); var Cx1 = double.Parse(list[0].X.Value) * Globals.px96 * 1.0 / Globals.px914400; var Cy1 = double.Parse(list[0].Y.Value) * Globals.px96 * 1.0 / Globals.px914400; var Cx2 = double.Parse(list[1].X.Value) * Globals.px96 * 1.0 / Globals.px914400; var Cy2 = double.Parse(list[1].Y.Value) * Globals.px96 * 1.0 / Globals.px914400; var Cx3 = double.Parse(list[2].X.Value) * Globals.px96 * 1.0 / Globals.px914400; var Cy3 = double.Parse(list[2].Y.Value) * Globals.px96 * 1.0 / Globals.px914400; d += " C" + System.Math.Round(Cx1, Globals.degree) + "," + System.Math.Round(Cy1, Globals.degree) + " " + System.Math.Round(Cx2, Globals.degree) + "," + System.Math.Round(Cy2, Globals.degree) + " " + System.Math.Round(Cx3, Globals.degree) + "," + System.Math.Round(Cy3, Globals.degree); } } if (child is CubicBezierCurveTo cubicBezierCurveTo) { var elms = cubicBezierCurveTo.Elements(); if (elms != null) { var list = elms.ToList(); var Cx1 = double.Parse(list[0].X.Value) * Globals.px96 * 1.0 / Globals.px914400; var Cy1 = double.Parse(list[0].Y.Value) * Globals.px96 * 1.0 / Globals.px914400; var Cx2 = double.Parse(list[1].X.Value) * Globals.px96 * 1.0 / Globals.px914400; var Cy2 = double.Parse(list[1].Y.Value) * Globals.px96 * 1.0 / Globals.px914400; d += " Q" + System.Math.Round(Cx1, Globals.degree) + "," + System.Math.Round(Cy1, Globals.degree) + " " + System.Math.Round(Cx2, Globals.degree) + "," + System.Math.Round(Cy2, Globals.degree); } } } //是否关闭svg if (close != null) { d += " z"; } List shapes = new List() { new SvgPath { type = "path", // Fill = (!imgFillFlg ? (grndFillFlg ? "url(#linGrd_" + order + ")" : Fill.Color) : "url(#imgPtrn_" + order + ")"), //Stroke = "#" + shapeBorder.Color, //StrokeWidth = shapeBorder.Width + "", //StrokeDasharray = shapeBorder.Stroke, d=d, start=shape.style.border.headEnd!=null?shape.style.border.headEnd:null, end=shape.style.border.tailEnd!=null?shape.style.border.tailEnd:null, } }; shape.svg = new Svg { id=index+"", svgShape=shapes }; shape.shapeType = "custom"; } } var elmt = new List (); shape.uid = Shape.suid; elmt.Add(shape); return elmt; } public List DrawText() { double top = getTopForCurrentAnchoring(this.Shape.VerticalAlign, this.Shape.Texts); List Paragraphs = new List(); foreach (var par in Shape.Texts) { double paragraphTop = this.top + (this.Shape.VerticalAlign.Equals(DocumentFormat.OpenXml.Drawing.TextAnchoringTypeValues.Top) ? par.getSpaceBeforePoints() : 0); Paragraph paragraph= new HTEXLib.Paragraph { animatable = par.Animatable ,invisible=par.Invisible}; paragraph.style.position.y = paragraphTop; paragraph.style.position.x =this.left; paragraph.style.position.cx = width; paragraph.style.position.cy = height; paragraph.style.position.rot = rot; paragraph.style.vert = this.Shape.VerticalAlign.ToString(); paragraph.style.hori = par.Align; paragraph.style.writing = Shape.WritingMode; 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(); 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; Font font = new System.Drawing.Font(par.defaultRunProperties.FontFamily.ToString(), points); newTop = font.Height; } List processedElements = new List(); List texts = new List(); IEnumerable pPTRunProperties = breakTextsToShape(par); foreach (var text in pPTRunProperties) { float points = float.Parse(text.FontSize.ToString()) * 72.0F / 96.0F; 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); //Text must already be broken to lines //Size size = MeasureString(currentString, font); // if (size.Width > this.width - par.Indent - par.marginLeft - par.marginRight) // { // top += newTop; // left = 0; // fixLeftSpacingForAlignment(processedElements, par, font); // processedElements.Clear(); // } 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: 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 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 - Shape.LeftInset - Shape.RightInset) - combinedWidth) / 2; else if ("Right".Equals(par.Align)) currentLeft = (this.width - par.Indent - bulletOffset - par.marginLeft - par.marginRight - Shape.LeftInset - Shape.RightInset) - combinedWidth; foreach (HtexText textElement in textElements) { textElement.setLeft(currentLeft + par.Indent + bulletOffset + par.marginLeft + Shape.LeftInset); currentLeft += textElement.width; } } //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, 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 - Shape.LeftInset - Shape.RightInset) - combinedWidth) / 2; else if ("Right".Equals(par.Align)) firstLeft = (this.width - par.Indent - bulletOffset - par.marginLeft - par.marginRight - Shape.LeftInset - Shape.RightInset) - 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 + Shape.LeftInset + MeasureString(combinedText.ToString(), font)); if (textElement.PictureBullet) combinedWidth += textElement.bulletSize; else combinedText.Append(textElement.Text); } } private double getTopForCurrentAnchoring(DocumentFormat.OpenXml.Drawing.TextAnchoringTypeValues anchoring, LinkedList paragraphList) { if (anchoring.Equals(DocumentFormat.OpenXml.Drawing.TextAnchoringTypeValues.Top)) return Shape.TopInset; double combinedHeight = 0; foreach (PPTParagraph par in paragraphList) { int newTop = 0; IEnumerable splitText = breakTextsToShape(par); foreach (var text in splitText) { float points = float.Parse(text.FontSize.ToString()) * 72.0F / 96.0F; 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); if (text.isBreak) { combinedHeight += newTop; newTop = font.Height; continue; } newTop = font.Height > newTop ? font.Height : newTop; } combinedHeight += par.getLineSpacingInPointsFromFont(newTop); combinedHeight += par.getSpaceBeforePoints(newTop); combinedHeight += par.getSpaceAfterPoints(newTop); } combinedHeight += Shape.TopInset + Shape.BottomInset; if (anchoring.Equals(DocumentFormat.OpenXml.Drawing.TextAnchoringTypeValues.Bottom)) return this.height - combinedHeight; return (this.height - combinedHeight) / 2; //Center align } 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; 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, 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(); } } }