From 8c9c62fb746b1fa8a7da34216809d2363f075ee1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=BE=E5=90=AF=E5=B7=A5=E4=BD=9C=E5=AE=A4?= Date: Sun, 28 Jun 2026 11:45:45 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20SmfFillType=20?= =?UTF-8?q?=E7=BC=BA=E5=B0=91=20Image=20=E5=8F=8A=20EnbxReader=20XDocument?= =?UTF-8?q?/XElement=20=E7=B1=BB=E5=9E=8B=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Services/Conversion/ConversionEngine.cs | 121 +++ Better-EN5/Services/Conversion/EnbxReader.cs | 573 ++++++++++++ Better-EN5/Services/Conversion/EnbxWriter.cs | 400 +++++++++ .../Services/Conversion/PptxExporter.cs | 827 ++++++++++++++++++ .../Services/Conversion/PptxImporter.cs | 804 +++++++++++++++++ Better-EN5/Services/Conversion/SmfTypes.cs | 2 +- .../Services/CustomConversionService.cs | 535 +---------- Manager/MainWindow.xaml | 52 +- Manager/MainWindow.xaml.cs | 86 ++ .../Services/Conversion/ConversionEngine.cs | 121 +++ Manager/Services/Conversion/EnbxReader.cs | 573 ++++++++++++ Manager/Services/Conversion/EnbxWriter.cs | 400 +++++++++ Manager/Services/Conversion/PptxExporter.cs | 827 ++++++++++++++++++ Manager/Services/Conversion/PptxImporter.cs | 618 +++++++++++++ Manager/Services/Conversion/SmfTypes.cs | 305 +++++++ Manager/Services/CustomConversionService.cs | 534 +---------- 16 files changed, 5754 insertions(+), 1024 deletions(-) create mode 100644 Better-EN5/Services/Conversion/ConversionEngine.cs create mode 100644 Better-EN5/Services/Conversion/EnbxReader.cs create mode 100644 Better-EN5/Services/Conversion/EnbxWriter.cs create mode 100644 Better-EN5/Services/Conversion/PptxExporter.cs create mode 100644 Better-EN5/Services/Conversion/PptxImporter.cs create mode 100644 Manager/Services/Conversion/ConversionEngine.cs create mode 100644 Manager/Services/Conversion/EnbxReader.cs create mode 100644 Manager/Services/Conversion/EnbxWriter.cs create mode 100644 Manager/Services/Conversion/PptxExporter.cs create mode 100644 Manager/Services/Conversion/PptxImporter.cs create mode 100644 Manager/Services/Conversion/SmfTypes.cs diff --git a/Better-EN5/Services/Conversion/ConversionEngine.cs b/Better-EN5/Services/Conversion/ConversionEngine.cs new file mode 100644 index 0000000..fd321ff --- /dev/null +++ b/Better-EN5/Services/Conversion/ConversionEngine.cs @@ -0,0 +1,121 @@ +using System; +using System.IO; +using System.Threading.Tasks; + +namespace BetterEN5.Services.Conversion +{ + public class ConversionEngine + { + private readonly PptxImporter _pptxImporter = new(); + private readonly PptxExporter _pptxExporter = new(); + private readonly EnbxWriter _enbxWriter = new(); + private readonly EnbxReader _enbxReader = new(); + + public async Task ConvertPptxToEnbxAsync(string pptxPath, IProgress? progress = null) + { + if (!File.Exists(pptxPath)) + throw new FileNotFoundException("PPT 文件未找到", pptxPath); + + var outputPath = Path.ChangeExtension(pptxPath, ".enbx"); + return await Task.Run(() => + { + progress?.Report(new ConversionProgress(0, "读取 PPTX...")); + + var doc = _pptxImporter.Import(pptxPath); + + progress?.Report(new ConversionProgress(50, "写入 ENBX...")); + + _enbxWriter.Export(doc, outputPath); + + progress?.Report(new ConversionProgress(100, "转换完成")); + + return outputPath; + }); + } + + public async Task ConvertEnbxToPptxAsync(string enbxPath, IProgress? progress = null) + { + if (!File.Exists(enbxPath)) + throw new FileNotFoundException("ENBX 文件未找到", enbxPath); + + var outputPath = Path.ChangeExtension(enbxPath, ".pptx"); + return await Task.Run(() => + { + progress?.Report(new ConversionProgress(0, "读取 ENBX...")); + + var doc = _enbxReader.Import(enbxPath); + + progress?.Report(new ConversionProgress(50, "写入 PPTX...")); + + _pptxExporter.Export(doc, outputPath); + + progress?.Report(new ConversionProgress(100, "转换完成")); + + return outputPath; + }); + } + + public async Task LoadPptxAsync(string pptxPath) + { + return await Task.Run(() => _pptxImporter.Import(pptxPath)); + } + + public async Task LoadEnbxAsync(string enbxPath) + { + return await Task.Run(() => _enbxReader.Import(enbxPath)); + } + + public async Task SaveAsPptxAsync(SmfDocument doc, string outputPath) + { + await Task.Run(() => _pptxExporter.Export(doc, outputPath)); + } + + public async Task SaveAsEnbxAsync(SmfDocument doc, string outputPath) + { + await Task.Run(() => _enbxWriter.Export(doc, outputPath)); + } + + public async Task BatchConvertAsync(string[] inputPaths, string outputDir, bool toEnbx, IProgress? progress = null) + { + int total = inputPaths.Length; + for (int i = 0; i < total; i++) + { + var input = inputPaths[i]; + var fileName = Path.GetFileNameWithoutExtension(input); + var ext = toEnbx ? ".enbx" : ".pptx"; + var output = Path.Combine(outputDir, fileName + ext); + + if (toEnbx) + await ConvertPptxToEnbxAsync(input, progress); + else + await ConvertEnbxToPptxAsync(input, progress); + + progress?.Report(new ConversionProgress((i + 1) * 100 / total, $"({i + 1}/{total}) 完成")); + } + } + + public void FixConversionErrors() + { + var en5Path = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + "Seewo", "EasiNote5"); + var tempDir = Path.Combine(en5Path, "Temp", "Conversion"); + if (Directory.Exists(tempDir)) + { + try { Directory.Delete(tempDir, true); } catch { } + } + } + } + + public class ConversionProgress + { + public int Percentage { get; } + public string Message { get; } + + public ConversionProgress(int percentage, string message) + { + Percentage = percentage; + Message = message; + } + } +} diff --git a/Better-EN5/Services/Conversion/EnbxReader.cs b/Better-EN5/Services/Conversion/EnbxReader.cs new file mode 100644 index 0000000..42fd4f6 --- /dev/null +++ b/Better-EN5/Services/Conversion/EnbxReader.cs @@ -0,0 +1,573 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.IO.Packaging; +using System.Linq; +using System.Xml.Linq; + +namespace BetterEN5.Services.Conversion +{ + public class EnbxReader + { + private int _nextId; + + public SmfDocument Import(string enbxPath) + { + using var package = Package.Open(enbxPath, FileMode.Open, FileAccess.Read); + return Import(package); + } + + public SmfDocument Import(Package enbx) + { + _nextId = 1; + var doc = new SmfDocument(); + + ExtractMediaFiles(enbx, doc); + + var docPart = FindDocumentPart(enbx); + if (docPart != null) + { + var docXml = XDocument.Load(docPart.GetStream()); + ParseDocumentXml(docXml, doc, enbx); + } + else + { + var mainDocPart = enbx.GetPart( + PackUriHelper.CreatePartUri(new Uri("/document.xml", UriKind.Relative))); + var mainDoc = XDocument.Load(mainDocPart.GetStream()); + ParseSlideBasedFormat(mainDoc, doc, enbx); + } + + return doc; + } + + private void ExtractMediaFiles(Package enbx, SmfDocument doc) + { + foreach (var part in enbx.GetParts()) + { + var uri = part.Uri.ToString(); + if (uri.StartsWith("/media/", StringComparison.OrdinalIgnoreCase) || + uri.StartsWith("/ppt/media/", StringComparison.OrdinalIgnoreCase)) + { + var fileName = Path.GetFileName(uri); + using var stream = part.GetStream(); + var data = new byte[stream.Length]; + stream.Read(data, 0, data.Length); + doc.MediaFiles[fileName] = data; + doc.MediaContentTypes[fileName] = part.ContentType; + } + } + } + + private PackagePart? FindDocumentPart(Package enbx) + { + var relsUri = PackUriHelper.CreatePartUri(new Uri("/_rels/.rels", UriKind.Relative)); + if (!enbx.PartExists(relsUri)) return null; + + var relsPart = enbx.GetPart(relsUri); + var rels = XDocument.Load(relsPart.GetStream()); + foreach (var rel in rels.Root?.Elements("Relationship") ?? Enumerable.Empty()) + { + var type = (string)rel.Attribute("Type") ?? ""; + if (type.Contains("document") || type.Contains("officeDocument")) + { + var target = (string)rel.Attribute("Target") ?? ""; + var targetUri = PackUriHelper.CreatePartUri(new Uri(target, UriKind.Relative)); + if (enbx.PartExists(targetUri)) + return enbx.GetPart(targetUri); + } + } + return null; + } + + private void ParseDocumentXml(XElement docXml, SmfDocument doc, Package enbx) + { + var root = docXml.Root ?? docXml; + var enNs = root.GetDefaultNamespace(); + + var props = root.Element(enNs + "Properties"); + if (props != null) + { + var sw = (string?)props.Element(enNs + "SlideWidth"); + var sh = (string?)props.Element(enNs + "SlideHeight"); + doc.Properties.SlideWidth = sw != null ? double.Parse(sw, CultureInfo.InvariantCulture) : 12192000; + doc.Properties.SlideHeight = sh != null ? double.Parse(sh, CultureInfo.InvariantCulture) : 6858000; + doc.Properties.Title = (string?)props.Element(enNs + "Title") ?? ""; + doc.Properties.Author = (string?)props.Element(enNs + "Author") ?? ""; + } + + var board = root.Element("Board"); + if (board != null) + { + doc.Properties.SlideWidth = (double?)board.Element("SlideWidth") ?? 12192000; + doc.Properties.SlideHeight = (double?)board.Element("SlideHeight") ?? 6858000; + } + + var slidesEl = root.Element(enNs + "Slides") ?? root.Element("Slides"); + if (slidesEl != null) + { + int slideNum = 0; + foreach (var slideRef in slidesEl.Elements(enNs + "SlideRef")) + { + slideNum++; + var target = (string)slideRef ?? $"slides/slide{slideNum}.xml"; + var slidePart = FindSlidePart(enbx, target, slideNum); + if (slidePart != null) + { + var slideXml = XDocument.Load(slidePart.GetStream()); + ParseSlideXml(slideXml, doc, slideNum); + } + } + } + } + + private void ParseSlideBasedFormat(XDocument mainDoc, SmfDocument doc, Package enbx) + { + var root = mainDoc.Root; + if (root == null) return; + + var board = root.Element("Board"); + if (board != null) + { + doc.Properties.SlideWidth = (double?)board.Element("SlideWidth") ?? 12192000; + doc.Properties.SlideHeight = (double?)board.Element("SlideHeight") ?? 6858000; + } + + var slidesEl = root.Element("Slides"); + if (slidesEl != null) + { + int idx = 0; + foreach (var slideEl in slidesEl.Elements("SlideSaveInfo")) + { + idx++; + var slide = new SmfSlide + { + Id = (string)slideEl.Element("Id") ?? $"slide_{idx}", + }; + ParseLegacyElements(slideEl, slide); + doc.Slides.Add(slide); + } + } + } + + private void ParseSlideXml(XDocument slideXml, SmfDocument doc, int slideNum) + { + var root = slideXml.Root ?? slideXml; + var enNs = root.GetDefaultNamespace(); + var slide = new SmfSlide { Id = $"slide_{slideNum}" }; + + var bg = root.Element(enNs + "Background"); + if (bg != null) + { + var solidFill = bg.Element(enNs + "SolidFill"); + if (solidFill != null) + { + slide.Background.Type = SmfFillType.Solid; + var color = (string?)solidFill.Element(enNs + "Color"); + if (color != null) slide.Background.SolidColor = new SmfColor { Value = color }; + } + + var gradFill = bg.Element(enNs + "GradientFill"); + if (gradFill != null) + { + slide.Background.Type = SmfFillType.Gradient; + slide.Background.Gradient = new SmfGradientFill + { + Type = Enum.TryParse((string?)gradFill.Element(enNs + "Type"), out var gt) ? gt : SmfGradientType.Linear, + Angle = (double?)gradFill.Element(enNs + "Angle") ?? 0, + }; + foreach (var stop in gradFill.Elements(enNs + "Stop")) + { + slide.Background.Gradient.Stops.Add(new SmfGradientStop + { + Position = (double?)stop.Element(enNs + "Position") ?? 0, + Color = new SmfColor { Value = (string?)stop.Element(enNs + "Color") ?? "000000" }, + }); + } + } + + var imgFill = bg.Element(enNs + "ImageFill"); + if (imgFill != null) + { + slide.Background.Type = SmfFillType.Image; + slide.Background.ImageRef = (string?)imgFill.Element(enNs + "Source"); + } + } + + var elementsEl = root.Element(enNs + "Elements"); + if (elementsEl != null) + { + foreach (var elemEl in elementsEl.Elements()) + { + var elem = ParseElement(elemEl, enNs); + if (elem != null) slide.Elements.Add(elem); + } + } + + doc.Slides.Add(slide); + } + + private SmfElement? ParseElement(XElement elemEl, XNamespace enNs) + { + var localName = elemEl.Name.LocalName; + var id = (string)elemEl.Attribute("Id") ?? NextId(); + + switch (localName) + { + case "TextElement": + return ParseTextElement(elemEl, enNs, id); + case "ImageElement": + return ParseImageElement(elemEl, enNs, id); + case "ShapeElement": + return ParseShapeElement(elemEl, enNs, id); + case "TableElement": + return ParseTableElement(elemEl, enNs, id); + case "InkElement": + return ParseInkElement(elemEl, enNs, id); + default: + return null; + } + } + + private SmfTextElement ParseTextElement(XElement elem, XNamespace enNs, string id) + { + var te = new SmfTextElement { Id = id }; + te.Bounds = ParseBounds(elem, enNs); + te.Rotation = (double?)elem.Element(enNs + "Rotation") ?? 0; + te.Locked = (bool?)elem.Element(enNs + "Locked") ?? false; + + var rt = elem.Element(enNs + "RichText"); + if (rt != null) + { + te.RichText.LeftMargin = (double?)rt.Element(enNs + "LeftMargin") ?? 91440; + te.RichText.RightMargin = (double?)rt.Element(enNs + "RightMargin") ?? 91440; + te.RichText.TopMargin = (double?)rt.Element(enNs + "TopMargin") ?? 91440; + te.RichText.BottomMargin = (double?)rt.Element(enNs + "BottomMargin") ?? 91440; + te.RichText.WordWrap = (bool?)rt.Element(enNs + "WordWrap") ?? true; + + var vAlign = (string?)rt.Element(enNs + "VerticalAlignment"); + if (vAlign != null && Enum.TryParse(vAlign, out var va)) + te.RichText.VerticalAlignment = va; + + foreach (var para in rt.Elements(enNs + "Paragraph")) + { + var p = new SmfParagraph(); + var algn = (string?)para.Element(enNs + "Alignment"); + if (algn != null && Enum.TryParse(algn, out var ha)) + p.Alignment = ha; + p.SpaceBefore = (double?)para.Element(enNs + "SpaceBefore"); + p.SpaceAfter = (double?)para.Element(enNs + "SpaceAfter"); + p.LineSpacing = (double?)para.Element(enNs + "LineSpacing"); + + foreach (var run in para.Elements(enNs + "Run")) + { + var r = new SmfRun + { + Text = (string?)run.Element(enNs + "Text") ?? "", + }; + var fmt = run.Element(enNs + "Formatting"); + if (fmt != null) + { + r.Formatting = new SmfFormatting + { + Size = (double?)fmt.Element(enNs + "Size"), + Bold = (bool?)fmt.Element(enNs + "Bold"), + Italic = (bool?)fmt.Element(enNs + "Italic"), + Underline = (bool?)fmt.Element(enNs + "Underline"), + Strikethrough = (bool?)fmt.Element(enNs + "Strikethrough"), + Color = ParseSmfColor(fmt.Element(enNs + "Color")), + Font = (string?)fmt.Element(enNs + "Font"), + }; + } + p.Runs.Add(r); + } + te.RichText.Paragraphs.Add(p); + } + } + + return te; + } + + private SmfImageElement ParseImageElement(XElement elem, XNamespace enNs, string id) + { + var ie = new SmfImageElement { Id = id }; + ie.Bounds = ParseBounds(elem, enNs); + ie.Rotation = (double?)elem.Element(enNs + "Rotation") ?? 0; + ie.Source = (string?)elem.Element(enNs + "Source") ?? ""; + + var crop = elem.Element(enNs + "Crop"); + if (crop != null) + { + ie.Crop = new SmfCropInfo + { + Left = (double?)crop.Element(enNs + "Left") ?? 0, + Right = (double?)crop.Element(enNs + "Right") ?? 0, + Top = (double?)crop.Element(enNs + "Top") ?? 0, + Bottom = (double?)crop.Element(enNs + "Bottom") ?? 0, + }; + } + + return ie; + } + + private SmfShapeElement ParseShapeElement(XElement elem, XNamespace enNs, string id) + { + var se = new SmfShapeElement { Id = id }; + se.Bounds = ParseBounds(elem, enNs); + se.Rotation = (double?)elem.Element(enNs + "Rotation") ?? 0; + se.Locked = (bool?)elem.Element(enNs + "Locked") ?? false; + + var shapeType = (string?)elem.Element(enNs + "ShapeType"); + if (shapeType != null && Enum.TryParse(shapeType, out var st)) + se.ShapeType = st; + + var fill = elem.Element(enNs + "Fill"); + if (fill != null) + { + var fillType = (string?)fill.Element(enNs + "Type"); + if (fillType == "Solid") + { + se.Fill.Type = SmfFillType.Solid; + var color = (string?)fill.Element(enNs + "Color"); + if (color != null) se.Fill.SolidColor = new SmfColor { Value = color }; + } + } + + var stroke = elem.Element(enNs + "Stroke"); + if (stroke != null) + { + se.Stroke = new SmfStroke + { + Color = ParseSmfColor(stroke.Element(enNs + "Color")), + Width = (double?)stroke.Element(enNs + "Width") ?? 1, + }; + var dash = (string?)stroke.Element(enNs + "DashStyle"); + if (dash != null && Enum.TryParse(dash, out var ds)) + se.Stroke.DashStyle = ds; + } + + var textContent = elem.Element(enNs + "TextContent"); + if (textContent != null) + { + var rt = new SmfRichText(); + foreach (var para in textContent.Elements(enNs + "Paragraph")) + { + var p = new SmfParagraph(); + foreach (var run in para.Elements(enNs + "Run")) + { + p.Runs.Add(new SmfRun + { + Text = (string?)run.Element(enNs + "Text") ?? "", + }); + } + rt.Paragraphs.Add(p); + } + se.TextContent = rt; + } + + return se; + } + + private SmfTableElement ParseTableElement(XElement elem, XNamespace enNs, string id) + { + var te = new SmfTableElement { Id = id }; + te.Bounds = ParseBounds(elem, enNs); + + var cols = elem.Element(enNs + "Columns"); + if (cols != null) + { + foreach (var col in cols.Elements(enNs + "Column")) + te.ColumnWidths.Add((double?)col.Attribute("Width") ?? 100000); + } + + var rows = elem.Element(enNs + "Rows"); + if (rows != null) + { + foreach (var row in rows.Elements(enNs + "Row")) + { + var r = new SmfTableRow + { + Height = (double?)row.Attribute("Height") ?? 500000, + }; + foreach (var cell in row.Elements(enNs + "Cell")) + { + var c = new SmfTableCell + { + RowSpan = (int?)cell.Attribute("RowSpan") ?? 1, + ColSpan = (int?)cell.Attribute("ColSpan") ?? 1, + }; + var cellRt = cell.Element(enNs + "RichText"); + if (cellRt != null) + { + var ct = new SmfRichText(); + foreach (var para in cellRt.Elements(enNs + "Paragraph")) + { + var p = new SmfParagraph(); + foreach (var run in para.Elements(enNs + "Run")) + { + p.Runs.Add(new SmfRun + { + Text = (string?)run.Element(enNs + "Text") ?? "", + }); + } + ct.Paragraphs.Add(p); + } + c.Content = ct; + } + r.Cells.Add(c); + } + te.Rows.Add(r); + } + } + + return te; + } + + private SmfInkElement ParseInkElement(XElement elem, XNamespace enNs, string id) + { + var ie = new SmfInkElement { Id = id }; + + foreach (var stroke in elem.Elements(enNs + "Stroke")) + { + var s = new SmfInkStroke + { + Color = new SmfColor { Value = (string?)stroke.Element(enNs + "Color") ?? "000000" }, + Width = (double?)stroke.Element(enNs + "Width") ?? 2, + IsHighlighter = (bool?)stroke.Element(enNs + "Highlighter") ?? false, + Transparency = (double?)stroke.Element(enNs + "Transparency") ?? 0, + }; + + var pts = stroke.Element(enNs + "Points"); + if (pts != null) + { + foreach (var pt in pts.Elements(enNs + "Point")) + { + s.Points.Add(new SmfInkPoint + { + X = (double)pt.Attribute("X"), + Y = (double)pt.Attribute("Y"), + Pressure = (double?)pt.Attribute("P") ?? 0.5, + }); + } + } + + ie.Strokes.Add(s); + } + + return ie; + } + + private void ParseLegacyElements(XElement slideEl, SmfSlide slide) + { + var width = (double?)slideEl.Element("Width") ?? 12192000; + var height = (double?)slideEl.Element("Height") ?? 6858000; + + var elements = slideEl.Element("Elements"); + if (elements == null) return; + + foreach (var elem in elements.Elements()) + { + var localName = elem.Name.LocalName; + if (localName == "TextSaveInfo") + { + var te = new SmfTextElement + { + Id = (string)elem.Element("Id") ?? NextId(), + Bounds = new SmfBounds + { + X = (double)elem.Element("X"), + Y = (double)elem.Element("Y"), + Width = (double)elem.Element("Width"), + Height = (double)elem.Element("Height"), + }, + Rotation = (double?)elem.Element("Rotation") ?? 0, + Locked = (bool?)elem.Element("Locked") ?? false, + }; + + var richText = elem.Element("RichText"); + if (richText != null) + { + te.RichText.LeftMargin = (double?)richText.Element("LeftMargin") ?? 91440; + te.RichText.RightMargin = (double?)richText.Element("RightMargin") ?? 91440; + te.RichText.TopMargin = (double?)richText.Element("TopMargin") ?? 91440; + te.RichText.BottomMargin = (double?)richText.Element("BottomMargin") ?? 91440; + + foreach (var para in richText.Elements("Paragraph")) + { + var p = new SmfParagraph(); + foreach (var run in para.Elements("Run")) + { + var r = new SmfRun + { + Text = (string?)run.Element("Text") ?? "", + }; + var fmt = run.Element("Formatting"); + if (fmt != null) + { + r.Formatting = new SmfFormatting + { + Size = (double?)fmt.Element("Size"), + Bold = (bool?)fmt.Element("Bold"), + Italic = (bool?)fmt.Element("Italic"), + Color = ParseColorAttr(fmt.Element("Color")), + Font = (string?)fmt.Element("Font"), + }; + } + p.Runs.Add(r); + } + te.RichText.Paragraphs.Add(p); + } + } + + slide.Elements.Add(te); + } + } + } + + private SmfBounds ParseBounds(XElement parent, XNamespace enNs) + { + var b = parent.Element(enNs + "Bounds"); + if (b == null) return new SmfBounds(); + return new SmfBounds + { + X = (double?)b.Element(enNs + "X") ?? 0, + Y = (double?)b.Element(enNs + "Y") ?? 0, + Width = (double?)b.Element(enNs + "Width") ?? 0, + Height = (double?)b.Element(enNs + "Height") ?? 0, + }; + } + + private SmfColor? ParseSmfColor(XElement? el) + { + if (el == null) return null; + return new SmfColor { Value = el.Value }; + } + + private SmfColor? ParseColorAttr(XElement? el) + { + if (el == null) return null; + return new SmfColor { Value = el.Value }; + } + + private PackagePart? FindSlidePart(Package enbx, string target, int slideNum) + { + var possiblePaths = new[] + { + target, + $"/{target}", + $"/slides/slide{slideNum}.xml", + $"slides/slide{slideNum}.xml", + }; + foreach (var path in possiblePaths) + { + var uri = PackUriHelper.CreatePartUri(new Uri(path, UriKind.Relative)); + if (enbx.PartExists(uri)) + return enbx.GetPart(uri); + } + return null; + } + + private string NextId() => $"elem_{_nextId++}"; + } +} diff --git a/Better-EN5/Services/Conversion/EnbxWriter.cs b/Better-EN5/Services/Conversion/EnbxWriter.cs new file mode 100644 index 0000000..272d76f --- /dev/null +++ b/Better-EN5/Services/Conversion/EnbxWriter.cs @@ -0,0 +1,400 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Packaging; +using System.Linq; +using System.Xml.Linq; + +namespace BetterEN5.Services.Conversion +{ + public class EnbxWriter + { + private static readonly XNamespace EN = "http://schemas.seewo.com/easinote/2016/document"; + private static readonly XNamespace DC = "http://purl.org/dc/elements/1.1/"; + + public void Export(SmfDocument doc, string enbxPath) + { + using var enbx = Package.Open(enbxPath, FileMode.Create, FileAccess.ReadWrite); + Export(doc, enbx); + } + + public void Export(SmfDocument doc, Package enbx) + { + WriteContentTypes(enbx); + WritePackageRels(enbx); + WriteMediaFiles(doc, enbx); + + var slideRelTargets = new List(); + for (int i = 0; i < doc.Slides.Count; i++) + { + var slideFileName = $"slides/slide{i + 1}.xml"; + slideRelTargets.Add(slideFileName); + + var slideDoc = BuildSlideXml(doc.Slides[i], doc, i + 1); + var slideUri = PackUriHelper.CreatePartUri(new Uri($"/{slideFileName}", UriKind.Relative)); + var slidePart = enbx.CreatePart(slideUri, "application/vnd.seewo.easinote.slide+xml"); + using (var sw = new StreamWriter(slidePart.GetStream())) + slideDoc.Save(sw); + } + + var rootDoc = BuildDocumentXml(doc, slideRelTargets); + var docUri = PackUriHelper.CreatePartUri(new Uri("/document.xml", UriKind.Relative)); + var docPart = enbx.CreatePart(docUri, "application/vnd.seewo.easinote.document+xml"); + using (var sw = new StreamWriter(docPart.GetStream())) + rootDoc.Save(sw); + } + + private XDocument BuildDocumentXml(SmfDocument doc, List slideTargets) + { + var docElem = new XElement(EN + "Document", + new XAttribute(XNamespace.Xmlns + "en", EN.NamespaceName), + new XAttribute(XNamespace.Xmlns + "dc", DC.NamespaceName)); + + var props = new XElement(EN + "Properties", + new XElement(EN + "Title", doc.Properties.Title ?? ""), + new XElement(EN + "Author", doc.Properties.Author ?? ""), + new XElement(EN + "Created", doc.Properties.Created?.ToString("o") ?? ""), + new XElement(EN + "Application", doc.Properties.Application ?? "Better-Seewo"), + new XElement(EN + "Version", "5.2.4.9855"), + new XElement(EN + "SlideWidth", doc.Properties.SlideWidth), + new XElement(EN + "SlideHeight", doc.Properties.SlideHeight)); + docElem.Add(props); + + var slidesElem = new XElement(EN + "Slides"); + foreach (var target in slideTargets) + { + slidesElem.Add(new XElement(EN + "SlideRef", target)); + } + docElem.Add(slidesElem); + + return new XDocument(new XDeclaration("1.0", "utf-8", "yes"), docElem); + } + + private XDocument BuildSlideXml(SmfSlide slide, SmfDocument doc, int slideNum) + { + var slideElem = new XElement(EN + "Slide", + new XAttribute("Id", slide.Id), + new XAttribute("Width", doc.Properties.SlideWidth), + new XAttribute("Height", doc.Properties.SlideHeight)); + + WriteBackground(slide, slideElem, doc, slideNum); + + var elementsElem = new XElement(EN + "Elements"); + foreach (var element in slide.Elements) + WriteElement(element, elementsElem, doc, slideNum); + slideElem.Add(elementsElem); + + return new XDocument(new XDeclaration("1.0", "utf-8", "yes"), slideElem); + } + + private void WriteBackground(SmfSlide slide, XElement slideElem, SmfDocument doc, int slideNum) + { + var bg = slide.Background; + if (bg.Type == SmfFillType.None) return; + + var bgElem = new XElement(EN + "Background"); + switch (bg.Type) + { + case SmfFillType.Solid when bg.SolidColor != null: + bgElem.Add(new XElement(EN + "SolidFill", + new XElement(EN + "Color", bg.SolidColor.Value))); + break; + case SmfFillType.Gradient when bg.Gradient != null: + var gradElem = new XElement(EN + "GradientFill", + new XElement(EN + "Type", bg.Gradient.Type.ToString()), + new XElement(EN + "Angle", bg.Gradient.Angle)); + foreach (var stop in bg.Gradient.Stops) + { + gradElem.Add(new XElement(EN + "Stop", + new XElement(EN + "Position", stop.Position), + new XElement(EN + "Color", stop.Color.Value))); + } + bgElem.Add(gradElem); + break; + case SmfFillType.Image when bg.ImageRef != null: + bgElem.Add(new XElement(EN + "ImageFill", + new XElement(EN + "Source", bg.ImageRef))); + break; + } + + if (bgElem.HasElements) + slideElem.Add(bgElem); + } + + private void WriteElement(SmfElement element, XElement elementsElem, SmfDocument doc, int slideNum) + { + switch (element) + { + case SmfTextElement te: + WriteTextElement(te, elementsElem); + break; + case SmfImageElement ie: + WriteImageElement(ie, elementsElem); + break; + case SmfShapeElement se: + WriteShapeElement(se, elementsElem); + break; + case SmfTableElement te: + WriteTableElement(te, elementsElem); + break; + case SmfInkElement ie: + WriteInkElement(ie, elementsElem); + break; + case SmfGroupElement ge: + break; + } + } + + private void WriteTextElement(SmfTextElement elem, XElement parent) + { + var xElem = new XElement(EN + "TextElement", + new XAttribute("Id", elem.Id)); + + AddBounds(xElem, elem.Bounds); + if (elem.Rotation != 0) + xElem.Add(new XElement(EN + "Rotation", elem.Rotation)); + xElem.Add(new XElement(EN + "Locked", elem.Locked)); + + var rtElem = new XElement(EN + "RichText"); + rtElem.Add(new XElement(EN + "LeftMargin", elem.RichText.LeftMargin)); + rtElem.Add(new XElement(EN + "RightMargin", elem.RichText.RightMargin)); + rtElem.Add(new XElement(EN + "TopMargin", elem.RichText.TopMargin)); + rtElem.Add(new XElement(EN + "BottomMargin", elem.RichText.BottomMargin)); + rtElem.Add(new XElement(EN + "VerticalAlignment", elem.RichText.VerticalAlignment.ToString())); + rtElem.Add(new XElement(EN + "WordWrap", elem.RichText.WordWrap)); + + foreach (var para in elem.RichText.Paragraphs) + { + var pElem = new XElement(EN + "Paragraph"); + if (para.Alignment != null) + pElem.Add(new XElement(EN + "Alignment", para.Alignment.ToString())); + if (para.SpaceBefore != null) + pElem.Add(new XElement(EN + "SpaceBefore", para.SpaceBefore.Value)); + if (para.SpaceAfter != null) + pElem.Add(new XElement(EN + "SpaceAfter", para.SpaceAfter.Value)); + if (para.LineSpacing != null) + pElem.Add(new XElement(EN + "LineSpacing", para.LineSpacing.Value)); + + foreach (var run in para.Runs) + { + var rElem = new XElement(EN + "Run", + new XElement(EN + "Text", run.Text)); + if (run.Formatting != null) + { + var fElem = new XElement(EN + "Formatting"); + if (run.Formatting.Size != null) fElem.Add(new XElement(EN + "Size", run.Formatting.Size.Value)); + if (run.Formatting.Bold != null) fElem.Add(new XElement(EN + "Bold", run.Formatting.Bold.Value)); + if (run.Formatting.Italic != null) fElem.Add(new XElement(EN + "Italic", run.Formatting.Italic.Value)); + if (run.Formatting.Underline != null) fElem.Add(new XElement(EN + "Underline", run.Formatting.Underline.Value)); + if (run.Formatting.Strikethrough != null) fElem.Add(new XElement(EN + "Strikethrough", run.Formatting.Strikethrough.Value)); + if (run.Formatting.Color != null) fElem.Add(new XElement(EN + "Color", run.Formatting.Color.Value)); + if (run.Formatting.Font != null) fElem.Add(new XElement(EN + "Font", run.Formatting.Font)); + if (fElem.HasElements) rElem.Add(fElem); + } + pElem.Add(rElem); + } + + if (pElem.HasElements) + rtElem.Add(pElem); + } + xElem.Add(rtElem); + + parent.Add(xElem); + } + + private void WriteImageElement(SmfImageElement elem, XElement parent) + { + var xElem = new XElement(EN + "ImageElement", + new XAttribute("Id", elem.Id)); + AddBounds(xElem, elem.Bounds); + if (elem.Rotation != 0) + xElem.Add(new XElement(EN + "Rotation", elem.Rotation)); + xElem.Add(new XElement(EN + "Source", elem.Source)); + + if (elem.Crop != null) + { + xElem.Add(new XElement(EN + "Crop", + new XElement(EN + "Left", elem.Crop.Left), + new XElement(EN + "Right", elem.Crop.Right), + new XElement(EN + "Top", elem.Crop.Top), + new XElement(EN + "Bottom", elem.Crop.Bottom))); + } + + parent.Add(xElem); + } + + private void WriteShapeElement(SmfShapeElement elem, XElement parent) + { + var xElem = new XElement(EN + "ShapeElement", + new XAttribute("Id", elem.Id)); + AddBounds(xElem, elem.Bounds); + if (elem.Rotation != 0) + xElem.Add(new XElement(EN + "Rotation", elem.Rotation)); + xElem.Add(new XElement(EN + "ShapeType", elem.ShapeType.ToString())); + + var fillElem = new XElement(EN + "Fill"); + fillElem.Add(new XElement(EN + "Type", elem.Fill.Type.ToString())); + if (elem.Fill.SolidColor != null) + fillElem.Add(new XElement(EN + "Color", elem.Fill.SolidColor.Value)); + if (fillElem.HasElements) xElem.Add(fillElem); + + if (elem.Stroke != null) + { + var sElem = new XElement(EN + "Stroke"); + if (elem.Stroke.Color != null) + sElem.Add(new XElement(EN + "Color", elem.Stroke.Color.Value)); + sElem.Add(new XElement(EN + "Width", elem.Stroke.Width)); + sElem.Add(new XElement(EN + "DashStyle", elem.Stroke.DashStyle.ToString())); + xElem.Add(sElem); + } + + if (elem.TextContent != null) + { + var rtElem = new XElement(EN + "TextContent"); + foreach (var para in elem.TextContent.Paragraphs) + { + var pElem = new XElement(EN + "Paragraph"); + foreach (var run in para.Runs) + { + pElem.Add(new XElement(EN + "Run", + new XElement(EN + "Text", run.Text))); + } + if (pElem.HasElements) rtElem.Add(pElem); + } + xElem.Add(rtElem); + } + + xElem.Add(new XElement(EN + "Locked", elem.Locked)); + + parent.Add(xElem); + } + + private void WriteTableElement(SmfTableElement elem, XElement parent) + { + var xElem = new XElement(EN + "TableElement", + new XAttribute("Id", elem.Id)); + AddBounds(xElem, elem.Bounds); + + var colsElem = new XElement(EN + "Columns"); + foreach (var w in elem.ColumnWidths) + colsElem.Add(new XElement(EN + "Column", new XAttribute("Width", w))); + xElem.Add(colsElem); + + var rowsElem = new XElement(EN + "Rows"); + foreach (var row in elem.Rows) + { + var rElem = new XElement(EN + "Row", new XAttribute("Height", row.Height)); + foreach (var cell in row.Cells) + { + var cElem = new XElement(EN + "Cell"); + if (cell.RowSpan > 1) cElem.Add(new XAttribute("RowSpan", cell.RowSpan)); + if (cell.ColSpan > 1) cElem.Add(new XAttribute("ColSpan", cell.ColSpan)); + if (cell.Content != null) + { + var rtElem = new XElement(EN + "RichText"); + foreach (var para in cell.Content.Paragraphs) + { + var pElem = new XElement(EN + "Paragraph"); + foreach (var run in para.Runs) + { + pElem.Add(new XElement(EN + "Run", + new XElement(EN + "Text", run.Text))); + } + if (pElem.HasElements) rtElem.Add(pElem); + } + if (rtElem.HasElements) cElem.Add(rtElem); + } + rElem.Add(cElem); + } + rowsElem.Add(rElem); + } + xElem.Add(rowsElem); + + parent.Add(xElem); + } + + private void WriteInkElement(SmfInkElement elem, XElement parent) + { + var xElem = new XElement(EN + "InkElement", + new XAttribute("Id", elem.Id)); + + foreach (var stroke in elem.Strokes) + { + var sElem = new XElement(EN + "Stroke"); + sElem.Add(new XElement(EN + "Color", stroke.Color.Value)); + sElem.Add(new XElement(EN + "Width", stroke.Width)); + sElem.Add(new XElement(EN + "Highlighter", stroke.IsHighlighter)); + sElem.Add(new XElement(EN + "Transparency", stroke.Transparency)); + + var ptsElem = new XElement(EN + "Points"); + foreach (var pt in stroke.Points) + { + ptsElem.Add(new XElement(EN + "Point", + new XAttribute("X", pt.X), + new XAttribute("Y", pt.Y), + new XAttribute("P", pt.Pressure))); + } + sElem.Add(ptsElem); + xElem.Add(sElem); + } + + parent.Add(xElem); + } + + private void AddBounds(XElement parent, SmfBounds bounds) + { + parent.Add(new XElement(EN + "Bounds", + new XElement(EN + "X", bounds.X), + new XElement(EN + "Y", bounds.Y), + new XElement(EN + "Width", bounds.Width), + new XElement(EN + "Height", bounds.Height))); + } + + private void WriteContentTypes(Package enbx) + { + var uri = PackUriHelper.CreatePartUri(new Uri("/[Content_Types].xml", UriKind.Relative)); + var part = enbx.CreatePart(uri, "application/xml"); + using (var sw = new StreamWriter(part.GetStream())) + { + sw.Write(""); + sw.Write(""); + sw.Write(""); + sw.Write(""); + sw.Write(""); + sw.Write(""); + sw.Write(""); + sw.Write(""); + sw.Write(""); + sw.Write(""); + sw.Write(""); + for (int i = 0; i < 100; i++) + sw.Write($""); + sw.Write(""); + } + } + + private void WritePackageRels(Package enbx) + { + var relsUri = PackUriHelper.CreatePartUri(new Uri("/_rels/.rels", UriKind.Relative)); + var relsPart = enbx.CreatePart(relsUri, "application/vnd.openxmlformats-package.relationships+xml"); + using (var sw = new StreamWriter(relsPart.GetStream())) + { + sw.Write(""); + sw.Write(""); + sw.Write(""); + } + } + + private void WriteMediaFiles(SmfDocument doc, Package enbx) + { + foreach (var (fileName, data) in doc.MediaFiles) + { + var mediaUri = PackUriHelper.CreatePartUri(new Uri($"/media/{fileName}", UriKind.Relative)); + var ct = doc.MediaContentTypes.TryGetValue(fileName, out var contentType) ? contentType : "application/octet-stream"; + var part = enbx.CreatePart(mediaUri, ct); + using var stream = part.GetStream(); + stream.Write(data, 0, data.Length); + } + } + } +} diff --git a/Better-EN5/Services/Conversion/PptxExporter.cs b/Better-EN5/Services/Conversion/PptxExporter.cs new file mode 100644 index 0000000..84d9056 --- /dev/null +++ b/Better-EN5/Services/Conversion/PptxExporter.cs @@ -0,0 +1,827 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Packaging; +using System.Linq; +using System.Xml.Linq; + +namespace BetterEN5.Services.Conversion +{ + public class PptxExporter + { + private static readonly XNamespace A = "http://schemas.openxmlformats.org/drawingml/2006/main"; + private static readonly XNamespace P = "http://schemas.openxmlformats.org/presentationml/2006/main"; + private static readonly XNamespace R = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"; + private static readonly XNamespace CT = "http://schemas.openxmlformats.org/package/2006/content-types"; + private static readonly XNamespace Rel = "http://schemas.openxmlformats.org/package/2006/relationships"; + + public void Export(SmfDocument doc, string pptxPath) + { + using var pptx = Package.Open(pptxPath, FileMode.Create, FileAccess.ReadWrite); + Export(doc, pptx); + } + + public void Export(SmfDocument doc, Package pptx) + { + CreateContentTypes(pptx, doc.Slides.Count); + CreateMinimalTheme(pptx); + CreateMinimalSlideMaster(pptx); + CreateMinimalSlideLayout(pptx); + CreatePresProps(pptx); + CreateViewProps(pptx); + WriteMediaFiles(doc, pptx); + + var presDoc = BuildPresentation(doc); + var (presRelDoc, slideRelDocs) = BuildRelations(doc, presDoc); + + var presXmlUri = PackUriHelper.CreatePartUri(new Uri("/ppt/presentation.xml", UriKind.Relative)); + var presPart = pptx.CreatePart(presXmlUri, "application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml"); + var presRelUri = PackUriHelper.CreatePartUri(new Uri("/ppt/_rels/presentation.xml.rels", UriKind.Relative)); + var presRelPart = pptx.CreatePart(presRelUri, "application/vnd.openxmlformats-package.relationships+xml"); + + using (var sw = new StreamWriter(presPart.GetStream())) + presDoc.Save(sw); + using (var sw = new StreamWriter(presRelPart.GetStream())) + presRelDoc.Save(sw); + + WriteSlides(doc, pptx, slideRelDocs); + + var rootRelUri = PackUriHelper.CreatePartUri(new Uri("/_rels/.rels", UriKind.Relative)); + var rootRelPart = pptx.CreatePart(rootRelUri, "application/vnd.openxmlformats-package.relationships+xml"); + using (var sw = new StreamWriter(rootRelPart.GetStream())) + { + sw.Write("", + Rel.NamespaceName, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"); + } + } + + private XDocument BuildPresentation(SmfDocument doc) + { + var pres = new XDocument(new XElement(P + "presentation", + new XAttribute(XNamespace.Xmlns + "a", A.NamespaceName), + new XAttribute(XNamespace.Xmlns + "r", R.NamespaceName), + new XAttribute(XNamespace.Xmlns + "p", P.NamespaceName), + new XElement(P + "sldMasterIdLst", + new XElement(P + "sldMasterId", new XAttribute("id", 2147483648), new XAttribute(R + "id", "rId1"))), + new XElement(P + "sldIdLst"), + new XElement(P + "sldSz", + new XAttribute("cx", doc.Properties.SlideWidth), + new XAttribute("cy", doc.Properties.SlideHeight)), + new XElement(P + "notesSz", + new XAttribute("cx", 6858000), new XAttribute("cy", 9144000)))); + return pres; + } + + private (XDocument presRels, List slideRels) BuildRelations(SmfDocument doc, XDocument presDoc) + { + var presRelNs = XNamespace.Get(Rel.NamespaceName); + var presRels = new XDocument(new XElement(presRelNs + "Relationships", + new XElement(presRelNs + "Relationship", new XAttribute("Id", "rId1"), + new XAttribute("Type", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster"), + new XAttribute("Target", "slideMasters/slideMaster1.xml")))); + + var slideIdLst = presDoc.Root?.Element(P + "sldIdLst"); + var slideRels = new List(); + + for (int i = 0; i < doc.Slides.Count; i++) + { + var rId = $"rId{i + 2}"; + var slideFileName = $"slides/slide{i + 1}.xml"; + + slideIdLst?.Add(new XElement(P + "sldId", + new XAttribute("id", 256 + i), + new XAttribute(R + "id", rId))); + + presRels.Root?.Add(new XElement(presRelNs + "Relationship", + new XAttribute("Id", rId), + new XAttribute("Type", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide"), + new XAttribute("Target", slideFileName))); + + var slideRel = new XDocument(new XElement(presRelNs + "Relationships", + new XElement(presRelNs + "Relationship", new XAttribute("Id", "rId1"), + new XAttribute("Type", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout"), + new XAttribute("Target", "../slideLayouts/slideLayout1.xml")))); + slideRels.Add(slideRel); + } + + return (presRels, slideRels); + } + + private void WriteSlides(SmfDocument doc, Package pptx, List slideRelDocs) + { + for (int i = 0; i < doc.Slides.Count; i++) + { + var slide = doc.Slides[i]; + var slideDoc = BuildSlideXml(slide, doc, i + 1); + + var slideUri = PackUriHelper.CreatePartUri(new Uri($"/ppt/slides/slide{i + 1}.xml", UriKind.Relative)); + var slidePart = pptx.CreatePart(slideUri, "application/vnd.openxmlformats-officedocument.presentationml.slide+xml"); + using (var sw = new StreamWriter(slidePart.GetStream())) + slideDoc.Save(sw); + + var slideRelUri = PackUriHelper.CreatePartUri(new Uri($"/ppt/slides/_rels/slide{i + 1}.xml.rels", UriKind.Relative)); + var slideRelPart = pptx.CreatePart(slideRelUri, "application/vnd.openxmlformats-package.relationships+xml"); + using (var sw = new StreamWriter(slideRelPart.GetStream())) + slideRelDocs[i].Save(sw); + } + } + + private XDocument BuildSlideXml(SmfSlide slide, SmfDocument doc, int slideNum) + { + var sld = new XDocument(new XElement(P + "sld", + new XAttribute(XNamespace.Xmlns + "a", A.NamespaceName), + new XAttribute(XNamespace.Xmlns + "r", R.NamespaceName), + new XAttribute(XNamespace.Xmlns + "p", P.NamespaceName))); + + var cSld = new XElement(P + "cSld"); + + WriteBackground(slide, cSld, doc, slideNum); + + var spTree = new XElement(P + "spTree"); + spTree.Add(new XElement(P + "nvGrpSpPr", + new XElement(P + "cNvPr", new XAttribute("id", 1), new XAttribute("name", "")), + new XElement(P + "cNvGrpSpPr"))); + spTree.Add(new XElement(P + "grpSpPr", + new XElement(A + "xfrm", + new XElement(A + "off", new XAttribute("x", 0), new XAttribute("y", 0)), + new XElement(A + "ext", new XAttribute("cx", 0), new XAttribute("cy", 0)), + new XElement(A + "chOff", new XAttribute("x", 0), new XAttribute("y", 0)), + new XElement(A + "chExt", new XAttribute("cx", 0), new XAttribute("cy", 0))))); + + int shapeId = 2; + foreach (var element in slide.Elements) + { + WriteElement(element, spTree, doc, slideNum, ref shapeId); + } + + cSld.Add(spTree); + sld.Root?.Add(cSld); + return sld; + } + + private void WriteBackground(SmfSlide slide, XElement cSld, SmfDocument doc, int slideNum) + { + var bg = slide.Background; + if (bg.Type == SmfFillType.None) return; + + var bgPr = new XElement(P + "bgPr"); + switch (bg.Type) + { + case SmfFillType.Solid when bg.SolidColor != null: + bgPr.Add(new XElement(A + "solidFill", + new XElement(A + "srgbClr", new XAttribute("val", bg.SolidColor.Value)))); + break; + case SmfFillType.Gradient when bg.Gradient != null: + bgPr.Add(WrGradient(bg.Gradient)); + break; + case SmfFillType.Image when bg.ImageRef != null: + if (doc.MediaFiles.ContainsKey(bg.ImageRef)) + { + var relId = $"bgRel_{slideNum}"; + bgPr.Add(new XElement(A + "blipFill", + new XElement(A + "blip", new XAttribute(R + "embed", relId)), + new XElement(A + "stretch", new XElement(A + "fillRect")))); + + var slideRelUri = PackUriHelper.CreatePartUri(new Uri($"/ppt/slides/_rels/slide{slideNum}.xml.rels", UriKind.Relative)); + if (slideRelUri != null) + { + } + } + break; + } + + if (bgPr.HasElements) + { + cSld.Add(new XElement(P + "bg", + bgPr)); + } + } + + private void WriteElement(SmfElement element, XElement spTree, SmfDocument doc, int slideNum, ref int shapeId) + { + switch (element) + { + case SmfTextElement textElem: + WriteTextShape(textElem, spTree, ref shapeId); + break; + case SmfImageElement imgElem: + WriteImage(imgElem, spTree, doc, slideNum, ref shapeId); + break; + case SmfShapeElement shapeElem: + WriteShape(shapeElem, spTree, ref shapeId); + break; + case SmfTableElement tableElem: + WriteTable(tableElem, spTree, ref shapeId); + break; + case SmfInkElement inkElem: + WriteInk(inkElem, spTree, ref shapeId); + break; + case SmfGroupElement groupElem: + break; + } + } + + private void WriteTextShape(SmfTextElement elem, XElement spTree, ref int shapeId) + { + var sp = new XElement(P + "sp"); + sp.Add(new XElement(P + "nvSpPr", + new XElement(P + "cNvPr", new XAttribute("id", shapeId++), new XAttribute("name", $"TextBox{shapeId - 1}")), + new XElement(P + "cNvSpPr", new XAttribute("txBox", 1)), + new XElement(P + "nvPr"))); + + var spPr = new XElement(P + "spPr"); + spPr.Add(WrXfrm(elem.Bounds, elem.Rotation)); + + if (elem.BackgroundFill?.Type == SmfFillType.Solid && elem.BackgroundFill.SolidColor != null) + { + spPr.Add(new XElement(A + "solidFill", + new XElement(A + "srgbClr", new XAttribute("val", elem.BackgroundFill.SolidColor.Value)))); + } + else + { + spPr.Add(new XElement(A + "noFill")); + } + + if (elem.Outline != null) + spPr.Add(WrStroke(elem.Outline)); + + spPr.Add(new XElement(A + "prstGeom", new XAttribute("prst", "rect"), + new XElement(A + "avLst"))); + sp.Add(spPr); + + sp.Add(WrTxBody(elem.RichText)); + spTree.Add(sp); + } + + private void WriteImage(SmfImageElement elem, XElement spTree, SmfDocument doc, int slideNum, ref int shapeId) + { + if (!doc.MediaFiles.ContainsKey(elem.Source)) return; + + var relId = $"imgRel_{shapeId}_{slideNum}"; + + var pic = new XElement(P + "pic"); + pic.Add(new XElement(P + "nvPicPr", + new XElement(P + "cNvPr", new XAttribute("id", shapeId++), new XAttribute("name", elem.Source)), + new XElement(P + "cNvPicPr"))); + + var blipFill = new XElement(P + "blipFill", + new XElement(A + "blip", new XAttribute(R + "embed", relId))); + + if (elem.Crop != null) + { + blipFill.Element(A + "blip")?.Add(new XElement(A + "srcRect", + new XAttribute("l", elem.Crop.Left), + new XAttribute("r", elem.Crop.Right), + new XAttribute("t", elem.Crop.Top), + new XAttribute("b", elem.Crop.Bottom))); + } + + blipFill.Add(new XElement(A + "stretch", new XElement(A + "fillRect"))); + pic.Add(blipFill); + + var spPr = new XElement(P + "spPr"); + spPr.Add(WrXfrm(elem.Bounds, elem.Rotation)); + spPr.Add(new XElement(A + "prstGeom", new XAttribute("prst", "rect"), + new XElement(A + "avLst"))); + pic.Add(spPr); + + spTree.Add(pic); + + AddImageRel(doc, slideNum, relId, elem.Source); + } + + private void AddImageRel(SmfDocument doc, int slideNum, string relId, string fileName) + { + var slideRelUri = PackUriHelper.CreatePartUri(new Uri($"/ppt/slides/_rels/slide{slideNum}.xml.rels", UriKind.Relative)); + } + + private void WriteShape(SmfShapeElement elem, XElement spTree, ref int shapeId) + { + var sp = new XElement(P + "sp"); + sp.Add(new XElement(P + "nvSpPr", + new XElement(P + "cNvPr", new XAttribute("id", shapeId++), new XAttribute("name", $"Shape{shapeId - 1}")), + new XElement(P + "cNvSpPr"), + new XElement(P + "nvPr"))); + + var spPr = new XElement(P + "spPr"); + spPr.Add(WrXfrm(elem.Bounds, elem.Rotation)); + + var shapePrst = ShapeTypeToPrst(elem.ShapeType); + if (elem.Fill.Type == SmfFillType.Solid && elem.Fill.SolidColor != null) + { + spPr.Add(new XElement(A + "solidFill", + new XElement(A + "srgbClr", new XAttribute("val", elem.Fill.SolidColor.Value)))); + } + else if (elem.Fill.Type == SmfFillType.Gradient && elem.Fill.Gradient != null) + { + spPr.Add(WrGradient(elem.Fill.Gradient)); + } + else + { + spPr.Add(new XElement(A + "noFill")); + } + + if (elem.Stroke != null) + spPr.Add(WrStroke(elem.Stroke)); + + spPr.Add(new XElement(A + "prstGeom", new XAttribute("prst", shapePrst), + new XElement(A + "avLst"))); + sp.Add(spPr); + + if (elem.TextContent != null) + sp.Add(WrTxBody(elem.TextContent)); + + spTree.Add(sp); + } + + private void WriteTable(SmfTableElement elem, XElement spTree, ref int shapeId) + { + var gf = new XElement(P + "graphicFrame"); + gf.Add(new XElement(P + "nvGraphicFramePr", + new XElement(P + "cNvPr", new XAttribute("id", shapeId++), new XAttribute("name", $"Table{shapeId - 1}")), + new XElement(P + "cNvGraphicFramePr"), + new XElement(P + "nvPr"))); + + var xfrm = new XElement(P + "xfrm"); + xfrm.Add(new XElement(A + "off", new XAttribute("x", elem.Bounds.X), new XAttribute("y", elem.Bounds.Y))); + xfrm.Add(new XElement(A + "ext", new XAttribute("cx", elem.Bounds.Width), new XAttribute("cy", elem.Bounds.Height))); + gf.Add(xfrm); + + var gv = new XElement(A + "graphic", + new XElement(A + "graphicData", new XAttribute("uri", "http://schemas.openxmlformats.org/drawingml/2006/table"))); + var tbl = new XElement(A + "tbl"); + + var gridCol = new XElement(A + "tblGrid"); + foreach (var w in elem.ColumnWidths) + gridCol.Add(new XElement(A + "gridCol", new XAttribute("w", w))); + tbl.Add(gridCol); + + foreach (var row in elem.Rows) + { + var tr = new XElement(A + "tr", new XAttribute("h", row.Height)); + foreach (var cell in row.Cells) + { + var tc = new XElement(A + "tc"); + var tcPr = new XElement(A + "tcPr"); + tcPr.Add(new XAttribute("marL", cell.LeftMargin)); + tcPr.Add(new XAttribute("marR", cell.RightMargin)); + tcPr.Add(new XAttribute("marT", cell.TopMargin)); + tcPr.Add(new XAttribute("marB", cell.BottomMargin)); + if (cell.RowSpan > 1) tcPr.Add(new XAttribute("rowSpan", cell.RowSpan)); + if (cell.ColSpan > 1) tcPr.Add(new XAttribute("gridSpan", cell.ColSpan)); + if (cell.MergeHorizontal) tcPr.Add(new XAttribute("hMerge", 1)); + if (cell.MergeVertical) tcPr.Add(new XAttribute("vMerge", 1)); + tcPr.Add(new XAttribute("anchor", cell.VerticalAlign switch + { + SmfVerticalAlignment.Top => "t", + SmfVerticalAlignment.Middle => "m", + SmfVerticalAlignment.Bottom => "b", + _ => "t", + })); + + if (cell.Background?.Type == SmfFillType.Solid && cell.Background.SolidColor != null) + { + tcPr.Add(new XElement(A + "solidFill", + new XElement(A + "srgbClr", new XAttribute("val", cell.Background.SolidColor.Value)))); + } + + tcPr.Add(WrTblBorder("lnL", cell.LeftBorder)); + tcPr.Add(WrTblBorder("lnR", cell.RightBorder)); + tcPr.Add(WrTblBorder("lnT", cell.TopBorder)); + tcPr.Add(WrTblBorder("lnB", cell.BottomBorder)); + tc.Add(tcPr); + + if (cell.Content != null) + tc.Add(WrCellTxBody(cell.Content)); + else + tc.Add(new XElement(A + "txBody", new XElement(A + "bodyPr"), new XElement(A + "p"))); + + tr.Add(tc); + } + tbl.Add(tr); + } + + gv.Element(A + "graphicData")?.Add(tbl); + gf.Add(gv); + spTree.Add(gf); + } + + private void WriteInk(SmfInkElement elem, XElement spTree, ref int shapeId) + { + foreach (var stroke in elem.Strokes) + { + var sp = new XElement(P + "sp"); + sp.Add(new XElement(P + "nvSpPr", + new XElement(P + "cNvPr", new XAttribute("id", shapeId++), new XAttribute("name", $"Ink{shapeId - 1}")), + new XElement(P + "cNvSpPr"), + new XElement(P + "nvPr"))); + + var minX = stroke.Points.Min(p => p.X); + var minY = stroke.Points.Min(p => p.Y); + var maxX = stroke.Points.Max(p => p.X); + var maxY = stroke.Points.Max(p => p.Y); + + var spPr = new XElement(P + "spPr"); + spPr.Add(new XElement(A + "xfrm", + new XElement(A + "off", new XAttribute("x", minX), new XAttribute("y", minY)), + new XElement(A + "ext", new XAttribute("cx", maxX - minX), new XAttribute("cy", maxY - minY)))); + spPr.Add(new XElement(A + "prstGeom", new XAttribute("prst", "rect"), + new XElement(A + "avLst"))); + + var ln = new XElement(A + "ln", new XAttribute("w", stroke.Width * 12700)); + ln.Add(new XElement(A + "solidFill", + new XElement(A + "srgbClr", new XAttribute("val", stroke.Color.Value)))); + if (stroke.IsHighlighter) + { + ln.Add(new XElement(A + "round")); + } + spPr.Add(ln); + + sp.Add(spPr); + + var txBody = new XElement(P + "txBody", + new XElement(A + "bodyPr"), + new XElement(A + "p")); + sp.Add(txBody); + + spTree.Add(sp); + } + } + + private XElement WrXfrm(SmfBounds bounds, double rotation) + { + var xfrm = new XElement(A + "xfrm"); + if (rotation != 0) + xfrm.Add(new XAttribute("rot", (int)(rotation * 60000))); + xfrm.Add(new XElement(A + "off", + new XAttribute("x", bounds.X), + new XAttribute("y", bounds.Y))); + xfrm.Add(new XElement(A + "ext", + new XAttribute("cx", bounds.Width), + new XAttribute("cy", bounds.Height))); + return xfrm; + } + + private XElement WrTxBody(SmfRichText rt) + { + var txBody = new XElement(P + "txBody"); + var bodyPr = new XElement(A + "bodyPr"); + + bodyPr.Add(new XAttribute("lIns", rt.LeftMargin)); + bodyPr.Add(new XAttribute("rIns", rt.RightMargin)); + bodyPr.Add(new XAttribute("tIns", rt.TopMargin)); + bodyPr.Add(new XAttribute("bIns", rt.BottomMargin)); + bodyPr.Add(new XAttribute("wrap", rt.WordWrap ? "square" : "none")); + bodyPr.Add(new XAttribute("anchor", rt.VerticalAlignment switch + { + SmfVerticalAlignment.Top => "t", + SmfVerticalAlignment.Middle => "m", + SmfVerticalAlignment.Bottom => "b", + _ => "t", + })); + if (rt.AutoFit) + bodyPr.Add(new XElement(A + "normAutofit")); + + txBody.Add(bodyPr); + + if (rt.Paragraphs.Count == 0) + { + txBody.Add(new XElement(A + "p")); + return txBody; + } + + foreach (var para in rt.Paragraphs) + { + var pElem = new XElement(A + "p"); + + if (para.Alignment != null || para.SpaceBefore != null || para.SpaceAfter != null || para.LineSpacing != null) + { + var pPr = new XElement(A + "pPr"); + if (para.Alignment != null) + { + pPr.Add(new XAttribute("algn", para.Alignment switch + { + SmfHorizontalAlignment.Left => "l", + SmfHorizontalAlignment.Center => "ctr", + SmfHorizontalAlignment.Right => "r", + SmfHorizontalAlignment.Justify => "just", + _ => "l", + })); + } + if (para.SpaceBefore != null) pPr.Add(new XAttribute("spcBef", para.SpaceBefore.Value)); + if (para.SpaceAfter != null) pPr.Add(new XAttribute("spcAft", para.SpaceAfter.Value)); + if (para.LineSpacing != null) + { + pPr.Add(new XElement(A + "lnSpc", + new XElement(A + "spcPct", new XAttribute("val", (int)(para.LineSpacing.Value * 100000))))); + } + pElem.Add(pPr); + } + + if (para.Runs.Count == 0) + { + pElem.Add(new XElement(A + "endParaRPr")); + } + else + { + foreach (var run in para.Runs) + { + var rElem = new XElement(A + "r"); + if (run.Formatting != null) + { + var rPr = new XElement(A + "rPr"); + if (run.Formatting.Size != null) + rPr.Add(new XAttribute("sz", (int)(run.Formatting.Size.Value * 100))); + if (run.Formatting.Bold == true) rPr.Add(new XAttribute("b", 1)); + if (run.Formatting.Italic == true) rPr.Add(new XAttribute("i", 1)); + if (run.Formatting.Underline == true) rPr.Add(new XAttribute("u", "sng")); + if (run.Formatting.Strikethrough == true) rPr.Add(new XAttribute("strike", "sngStrike")); + if (run.Formatting.Color != null) + rPr.Add(new XElement(A + "solidFill", + new XElement(A + "srgbClr", new XAttribute("val", run.Formatting.Color.Value)))); + if (run.Formatting.Font != null) + rPr.Add(new XElement(A + "latin", new XAttribute("typeface", run.Formatting.Font))); + if (rPr.HasAttributes || rPr.HasElements) + rElem.Add(rPr); + } + rElem.Add(new XElement(A + "t", new XText(run.Text))); + pElem.Add(rElem); + } + } + + txBody.Add(pElem); + } + + return txBody; + } + + private XElement WrCellTxBody(SmfRichText rt) + { + var txBody = new XElement(A + "txBody"); + var bodyPr = new XElement(A + "bodyPr"); + bodyPr.Add(new XAttribute("anchor", rt.VerticalAlignment switch + { + SmfVerticalAlignment.Top => "t", + SmfVerticalAlignment.Middle => "m", + SmfVerticalAlignment.Bottom => "b", + _ => "t", + })); + txBody.Add(bodyPr); + + if (rt.Paragraphs.Count == 0) + { + txBody.Add(new XElement(A + "p")); + } + else + { + foreach (var para in rt.Paragraphs) + { + var pElem = new XElement(A + "p"); + if (para.Runs.Count == 0) + { + pElem.Add(new XElement(A + "endParaRPr")); + } + else + { + foreach (var run in para.Runs) + { + var rElem = new XElement(A + "r"); + if (run.Formatting != null) + { + var rPr = new XElement(A + "rPr"); + if (run.Formatting.Size != null) + rPr.Add(new XAttribute("sz", (int)(run.Formatting.Size.Value * 100))); + if (run.Formatting.Bold == true) rPr.Add(new XAttribute("b", 1)); + if (run.Formatting.Italic == true) rPr.Add(new XAttribute("i", 1)); + if (run.Formatting.Color != null) + rPr.Add(new XElement(A + "solidFill", + new XElement(A + "srgbClr", new XAttribute("val", run.Formatting.Color.Value)))); + if (run.Formatting.Font != null) + rPr.Add(new XElement(A + "latin", new XAttribute("typeface", run.Formatting.Font))); + if (rPr.HasAttributes || rPr.HasElements) + rElem.Add(rPr); + } + rElem.Add(new XElement(A + "t", new XText(run.Text))); + pElem.Add(rElem); + } + } + txBody.Add(pElem); + } + } + + return txBody; + } + + private XElement WrGradient(SmfGradientFill grad) + { + var gradFill = new XElement(A + "gradFill"); + var gsLst = new XElement(A + "gsLst"); + foreach (var stop in grad.Stops) + { + var gs = new XElement(A + "gs", + new XAttribute("pos", (int)(stop.Position * 1000))); + gs.Add(new XElement(A + "srgbClr", new XAttribute("val", stop.Color.Value))); + gsLst.Add(gs); + } + gradFill.Add(gsLst); + + if (grad.Type == SmfGradientType.Linear) + { + gradFill.Add(new XElement(A + "lin", + new XAttribute("ang", (int)(grad.Angle * 60000)), + new XAttribute("scaled", 1))); + } + else + { + gradFill.Add(new XElement(A + "path", + new XAttribute("path", grad.Type switch + { + SmfGradientType.Radial => "circle", + SmfGradientType.Rectangular => "rect", + SmfGradientType.Path => "shape", + _ => "circle", + }))); + } + + return gradFill; + } + + private XElement WrStroke(SmfStroke stroke) + { + var ln = new XElement(A + "ln", + new XAttribute("w", stroke.Width)); + if (stroke.Color != null) + { + ln.Add(new XElement(A + "solidFill", + new XElement(A + "srgbClr", new XAttribute("val", stroke.Color.Value)))); + } + if (stroke.DashStyle != SmfStrokeDashStyle.Solid) + { + ln.Add(new XElement(A + "prstDash", + new XAttribute("val", stroke.DashStyle switch + { + SmfStrokeDashStyle.Dash => "dash", + SmfStrokeDashStyle.Dot => "dot", + SmfStrokeDashStyle.DashDot => "dashDot", + SmfStrokeDashStyle.LongDash => "lgDash", + SmfStrokeDashStyle.LongDashDot => "lgDashDot", + SmfStrokeDashStyle.DashDotDot => "dashDotDot", + _ => "solid", + }))); + } + return ln; + } + + private XElement WrTblBorder(string name, SmfTableBorder border) + { + var ln = new XElement(A + name, + new XAttribute("w", border.Width)); + if (border.Style != SmfTableBorderStyle.None) + { + ln.Add(new XAttribute("cmpd", "sng")); + var dashVal = border.Style switch + { + SmfTableBorderStyle.Dashed => "dash", + SmfTableBorderStyle.Dotted => "dot", + _ => "solid", + }; + ln.Add(new XElement(A + "prstDash", new XAttribute("val", dashVal))); + } + ln.Add(new XElement(A + "solidFill", + new XElement(A + "srgbClr", new XAttribute("val", border.Color)))); + return ln; + } + + private void WriteMediaFiles(SmfDocument doc, Package pptx) + { + foreach (var (fileName, data) in doc.MediaFiles) + { + var mediaUri = PackUriHelper.CreatePartUri(new Uri($"/ppt/media/{fileName}", UriKind.Relative)); + var contentType = doc.MediaContentTypes.TryGetValue(fileName, out var ct) ? ct : "application/octet-stream"; + var part = pptx.CreatePart(mediaUri, contentType); + using var stream = part.GetStream(); + stream.Write(data, 0, data.Length); + } + } + + private static string ShapeTypeToPrst(SmfShapeType type) + { + return type switch + { + SmfShapeType.Rectangle => "rect", + SmfShapeType.Ellipse => "ellipse", + SmfShapeType.Triangle => "triangle", + SmfShapeType.RoundedRectangle => "roundRect", + SmfShapeType.Diamond => "diamond", + SmfShapeType.Parallelogram => "parallelogram", + SmfShapeType.Trapezoid => "trapezoid", + SmfShapeType.Pentagon => "pentagon", + SmfShapeType.Hexagon => "hexagon", + SmfShapeType.Octagon => "octagon", + SmfShapeType.Star => "star5", + SmfShapeType.ArrowRight => "arrow", + SmfShapeType.ArrowLeft => "leftArrow", + SmfShapeType.ArrowUp => "upArrow", + SmfShapeType.ArrowDown => "downArrow", + SmfShapeType.Line => "line", + SmfShapeType.LineArrow => "straightConnector1", + SmfShapeType.Cloud => "cloud", + SmfShapeType.Heart => "heart", + SmfShapeType.Cross => "cross", + SmfShapeType.Chevron => "chevron", + SmfShapeType.Pie => "pie", + SmfShapeType.BlockArc => "blockArc", + SmfShapeType.RightTriangle => "rtTriangle", + SmfShapeType.RegularPentagon => "pentagon", + _ => "rect", + }; + } + + private void CreateContentTypes(Package pptx, int slideCount) + { + var uri = PackUriHelper.CreatePartUri(new Uri("/[Content_Types].xml", UriKind.Relative)); + var part = pptx.CreatePart(uri, "application/xml"); + using (var sw = new StreamWriter(part.GetStream())) + { + sw.Write("", CT.NamespaceName); + sw.Write(""); + sw.Write(""); + sw.Write(""); + sw.Write(""); + sw.Write(""); + sw.Write(""); + sw.Write(""); + sw.Write(""); + sw.Write(""); + sw.Write(""); + sw.Write(""); + sw.Write(""); + sw.Write(""); + sw.Write(""); + for (int i = 0; i < slideCount; i++) + sw.Write($""); + sw.Write(""); + sw.Write(""); + sw.Write(""); + sw.Write(""); + } + } + + private void CreateMinimalTheme(Package pptx) + { + var themeUri = PackUriHelper.CreatePartUri(new Uri("/ppt/theme/theme1.xml", UriKind.Relative)); + var themePart = pptx.CreatePart(themeUri, "application/vnd.openxmlformats-officedocument.theme+xml"); + using (var sw = new StreamWriter(themePart.GetStream())) + { + sw.Write("", + A.NamespaceName); + } + } + + private void CreateMinimalSlideMaster(Package pptx) + { + var masterUri = PackUriHelper.CreatePartUri(new Uri("/ppt/slideMasters/slideMaster1.xml", UriKind.Relative)); + var masterPart = pptx.CreatePart(masterUri, "application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml"); + using (var sw = new StreamWriter(masterPart.GetStream())) + { + sw.Write("", + A.NamespaceName, R.NamespaceName, P.NamespaceName); + } + + var masterRelUri = PackUriHelper.CreatePartUri(new Uri("/ppt/slideMasters/_rels/slideMaster1.xml.rels", UriKind.Relative)); + var masterRelPart = pptx.CreatePart(masterRelUri, "application/vnd.openxmlformats-package.relationships+xml"); + using (var sw = new StreamWriter(masterRelPart.GetStream())) + { + sw.Write("", + Rel.NamespaceName, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout"); + } + } + + private void CreateMinimalSlideLayout(Package pptx) + { + var layoutUri = PackUriHelper.CreatePartUri(new Uri("/ppt/slideLayouts/slideLayout1.xml", UriKind.Relative)); + var layoutPart = pptx.CreatePart(layoutUri, "application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml"); + using (var sw = new StreamWriter(layoutPart.GetStream())) + { + sw.Write("", + A.NamespaceName, R.NamespaceName, P.NamespaceName); + } + } + + private void CreatePresProps(Package pptx) + { + var uri = PackUriHelper.CreatePartUri(new Uri("/ppt/presProps.xml", UriKind.Relative)); + var part = pptx.CreatePart(uri, "application/vnd.openxmlformats-officedocument.presentationml.presProps+xml"); + using (var sw = new StreamWriter(part.GetStream())) + sw.Write("", P.NamespaceName); + } + + private void CreateViewProps(Package pptx) + { + var uri = PackUriHelper.CreatePartUri(new Uri("/ppt/viewProps.xml", UriKind.Relative)); + var part = pptx.CreatePart(uri, "application/vnd.openxmlformats-officedocument.presentationml.viewProps+xml"); + using (var sw = new StreamWriter(part.GetStream())) + sw.Write("", P.NamespaceName); + } + } +} diff --git a/Better-EN5/Services/Conversion/PptxImporter.cs b/Better-EN5/Services/Conversion/PptxImporter.cs new file mode 100644 index 0000000..d3a336e --- /dev/null +++ b/Better-EN5/Services/Conversion/PptxImporter.cs @@ -0,0 +1,804 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Packaging; +using System.Linq; +using System.Xml.Linq; + +namespace BetterEN5.Services.Conversion +{ + public class PptxImporter + { + private static readonly XNamespace A = "http://schemas.openxmlformats.org/drawingml/2006/main"; + private static readonly XNamespace P = "http://schemas.openxmlformats.org/presentationml/2006/main"; + private static readonly XNamespace R = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"; + + private int _nextId; + + public SmfDocument Import(string pptxPath) + { + using var package = Package.Open(pptxPath, FileMode.Open, FileAccess.Read); + return Import(package); + } + + public SmfDocument Import(Package pptx) + { + _nextId = 1; + var doc = new SmfDocument(); + var pres = GetPresentation(pptx); + var size = GetSlideSize(pres); + doc.Properties.SlideWidth = size.Item1; + doc.Properties.SlideHeight = size.Item2; + doc.Properties.Title = GetProperty(pres, P + "title") ?? ""; + ExtractMediaFiles(pptx, doc); + + var slideRels = GetSlideRelations(pptx, pres); + foreach (var (relId, target, slideNum) in slideRels) + { + var slidePart = pptx.GetPart(PackUriHelper.CreatePartUri(new Uri(target, UriKind.Relative))); + var slideXml = XDocument.Load(slidePart.GetStream()); + var slide = new SmfSlide { Id = $"slide_{slideNum}" }; + + ParseBackground(slideXml, slide, pptx); + ParseElements(slideXml, slide, pptx, doc); + + doc.Slides.Add(slide); + } + + return doc; + } + + private void ExtractMediaFiles(Package pptx, SmfDocument doc) + { + foreach (var part in pptx.GetParts()) + { + var uri = part.Uri.ToString(); + if (uri.StartsWith("/ppt/media/", StringComparison.OrdinalIgnoreCase)) + { + var fileName = Path.GetFileName(uri); + using var stream = part.GetStream(); + var data = new byte[stream.Length]; + stream.Read(data, 0, data.Length); + doc.MediaFiles[fileName] = data; + doc.MediaContentTypes[fileName] = part.ContentType; + } + } + } + + private void ParseBackground(XDocument slideXml, SmfSlide slide, Package pptx) + { + var bg = slideXml.Root?.Element(P + "cSld")?.Element(P + "bg"); + if (bg == null) return; + + var bgPr = bg.Element(P + "bgPr"); + if (bgPr == null) return; + + var solidFill = bgPr.Element(A + "solidFill"); + if (solidFill != null) + { + slide.Background.Type = SmfFillType.Solid; + slide.Background.SolidColor = ParseColor(solidFill); + return; + } + + var gradFill = bgPr.Element(A + "gradFill"); + if (gradFill != null) + { + slide.Background.Type = SmfFillType.Gradient; + slide.Background.Gradient = ParseGradient(gradFill); + return; + } + + var blipFill = bgPr.Element(A + "blipFill"); + if (blipFill != null) + { + var blip = blipFill.Element(A + "blip"); + var embed = blip?.Attribute(R + "embed")?.Value; + if (embed != null) + { + var relsPart = GetSlideRelsPart(pptx, slide.Id); + if (relsPart != null) + { + var rels = XDocument.Load(relsPart.GetStream()); + var rel = rels.Root?.Elements("Relationship") + .FirstOrDefault(r => (string)r.Attribute("Id") == embed); + if (rel != null) + { + var target = (string)rel.Attribute("Target"); + slide.Background.Type = SmfFillType.Image; + slide.Background.ImageRef = Path.GetFileName(target); + } + } + } + } + } + + private PackagePart? GetSlideRelsPart(Package pptx, string slideId) + { + var num = slideId.Replace("slide_", ""); + var relsUri = PackUriHelper.CreatePartUri( + new Uri($"/ppt/slides/_rels/slide{num}.xml.rels", UriKind.Relative)); + if (pptx.PartExists(relsUri)) + return pptx.GetPart(relsUri); + return null; + } + + private void ParseElements(XDocument slideXml, SmfSlide slide, Package pptx, SmfDocument doc) + { + var spTree = slideXml.Root?.Element(P + "cSld")?.Element(P + "spTree"); + if (spTree == null) return; + + var orderedElements = spTree.Elements().ToList(); + var grouped = new Dictionary>(); + + foreach (var elem in orderedElements) + { + var nvPr = elem.Element(P + "nvSpPr") ?? elem.Element(P + "nvGrpSpPr") ?? elem.Element(P + "nvPicPr"); + var cNvPr = nvPr?.Element(P + "cNvPr"); + var name = (string)cNvPr?.Attribute("name") ?? ""; + var idAttr = (string)cNvPr?.Attribute("id") ?? ""; + + string elemName = elem.Name.LocalName; + switch (elemName) + { + case "sp": + ParseShapeElement(elem, slide, pptx); + break; + case "pic": + ParseImageElement(elem, slide, pptx); + break; + case "graphicFrame": + ParseTableElement(elem, slide); + break; + case "grpSp": + ParseGroupElement(elem, slide, pptx); + break; + } + } + } + + private void ParseShapeElement(XElement sp, SmfSlide slide, Package pptx) + { + var nvSpPr = sp.Element(P + "nvSpPr"); + var cNvSpPr = nvSpPr?.Element(P + "cNvSpPr"); + var isTextBox = cNvSpPr?.Attribute("txBox") != null; + var txBody = sp.Element(P + "txBody"); + + var xfrm = sp.Element(P + "spPr")?.Element(A + "xfrm"); + if (xfrm == null) return; + + var bounds = ParseBounds(xfrm); + var rotation = (double?)xfrm?.Attribute("rot") ?? 0; + + var spPr = sp.Element(P + "spPr"); + + var prstGeom = spPr?.Element(A + "prstGeom"); + var shapeType = prstGeom != null ? ParseShapeType((string)prstGeom.Attribute("prst") ?? "") : SmfShapeType.NoShape; + var hasCustomGeom = spPr?.Element(A + "custGeom") != null; + + var fill = ParseFill(spPr); + var stroke = ParseStroke(spPr); + + if (isTextBox || (txBody != null && shapeType == SmfShapeType.NoShape)) + { + var elem = new SmfTextElement + { + Id = NextId(), + Bounds = bounds, + Rotation = rotation / 60000.0, + RichText = ParseRichText(txBody), + BackgroundFill = fill, + Outline = stroke, + }; + slide.Elements.Add(elem); + } + else + { + var elem = new SmfShapeElement + { + Id = NextId(), + Bounds = bounds, + Rotation = rotation / 60000.0, + ShapeType = shapeType, + Fill = fill, + Stroke = stroke, + }; + if (txBody != null) + elem.TextContent = ParseRichText(txBody); + slide.Elements.Add(elem); + } + } + + private void ParseImageElement(XElement pic, SmfSlide slide, Package pptx) + { + var xfrm = pic.Element(P + "blipFill")?.Element(A + "blip"); + var spPr = pic.Element(P + "spPr"); + var prXfrm = spPr?.Element(A + "xfrm"); + if (prXfrm == null) return; + + var bounds = ParseBounds(prXfrm); + var rotation = (double?)prXfrm?.Attribute("rot") ?? 0; + + var blip = xfrm?.Element(A + "blip") ?? pic.Element(P + "blipFill")?.Element(A + "blip"); + var embed = blip?.Attribute(R + "embed")?.Value; + var hyperlink = blip?.Attribute(R + "hyperlink")?.Value; + + if (embed == null) return; + + var relsPart = GetSlideRelsPart(pptx, slide.Id); + if (relsPart == null) return; + var rels = XDocument.Load(relsPart.GetStream()); + var rel = rels.Root?.Elements("Relationship") + .FirstOrDefault(r => (string)r.Attribute("Id") == embed); + if (rel == null) return; + + var target = (string)rel.Attribute("Target"); + var fullTarget = target; + + if (fullTarget.StartsWith("../media/", StringComparison.OrdinalIgnoreCase)) + fullTarget = fullTarget.Substring("../".Length); + + var srcPath = fullTarget; + var fileName = Path.GetFileName(srcPath); + + var elem = new SmfImageElement + { + Id = NextId(), + Bounds = bounds, + Rotation = rotation / 60000.0, + Source = fileName, + ContentType = guessContentType(fileName), + }; + + var cropRect = blip?.Element(A + "srcRect"); + if (cropRect != null) + { + elem.Crop = new SmfCropInfo + { + Left = (double?)cropRect.Attribute("l") ?? 0, + Right = (double?)cropRect.Attribute("r") ?? 0, + Top = (double?)cropRect.Attribute("t") ?? 0, + Bottom = (double?)cropRect.Attribute("b") ?? 0, + }; + } + + slide.Elements.Add(elem); + } + + private void ParseTableElement(XElement graphicFrame, SmfSlide slide) + { + var tbl = graphicFrame.Descendants(A + "tbl").FirstOrDefault(); + if (tbl == null) return; + + var xfrm = graphicFrame.Element(P + "xfrm"); + if (xfrm == null) + { + var spPr = graphicFrame.Element(P + "spPr"); + xfrm = spPr?.Element(A + "xfrm"); + } + if (xfrm == null) return; + + var bounds = ParseBounds(xfrm); + + var table = new SmfTableElement + { + Id = NextId(), + Bounds = bounds, + }; + + var gridCol = tbl.Element(A + "tblGrid"); + if (gridCol != null) + { + foreach (var col in gridCol.Elements(A + "gridCol")) + { + table.ColumnWidths.Add((double)col.Attribute("w")); + } + } + + foreach (var tr in tbl.Elements(A + "tr")) + { + var row = new SmfTableRow + { + Height = (double)tr.Attribute("h"), + }; + foreach (var tc in tr.Elements(A + "tc")) + { + var cell = new SmfTableCell(); + var txBody = tc.Element(A + "txBody"); + if (txBody != null) + cell.Content = ParseRichText(txBody); + + var tcPr = tc.Element(A + "tcPr"); + if (tcPr != null) + { + cell.RowSpan = (int?)tcPr.Attribute("rowSpan") ?? 1; + cell.ColSpan = (int?)tcPr.Attribute("gridSpan") ?? 1; + cell.MergeHorizontal = tcPr.Attribute("hMerge") != null; + cell.MergeVertical = tcPr.Attribute("vMerge") != null; + cell.LeftMargin = (double?)tcPr.Attribute("marL") ?? 45720; + cell.RightMargin = (double?)tcPr.Attribute("marR") ?? 45720; + cell.TopMargin = (double?)tcPr.Attribute("marT") ?? 0; + cell.BottomMargin = (double?)tcPr.Attribute("marB") ?? 0; + + var anchor = (string)tcPr.Attribute("anchor") ?? "t"; + cell.VerticalAlign = anchor switch + { + "t" => SmfVerticalAlignment.Top, + "m" => SmfVerticalAlignment.Middle, + "b" => SmfVerticalAlignment.Bottom, + _ => SmfVerticalAlignment.Top, + }; + + var solidFill = tcPr.Element(A + "solidFill"); + if (solidFill != null) + cell.Background = new SmfFill { Type = SmfFillType.Solid, SolidColor = ParseColor(solidFill) }; + + var lnL = tcPr.Element(A + "lnL"); + var lnR = tcPr.Element(A + "lnR"); + var lnT = tcPr.Element(A + "lnT"); + var lnB = tcPr.Element(A + "lnB"); + if (lnL != null) cell.LeftBorder = ParseTableBorder(lnL); + if (lnR != null) cell.RightBorder = ParseTableBorder(lnR); + if (lnT != null) cell.TopBorder = ParseTableBorder(lnT); + if (lnB != null) cell.BottomBorder = ParseTableBorder(lnB); + } + + row.Cells.Add(cell); + } + table.Rows.Add(row); + } + + slide.Elements.Add(table); + } + + private void ParseGroupElement(XElement grpSp, SmfSlide slide, Package pptx) + { + var elem = new SmfGroupElement + { + Id = NextId(), + }; + slide.Elements.Add(elem); + } + + private SmfBounds ParseBounds(XElement xfrm) + { + var off = xfrm.Element(A + "off"); + var ext = xfrm.Element(A + "ext"); + return new SmfBounds + { + X = off != null ? (double)off.Attribute("x") : 0, + Y = off != null ? (double)off.Attribute("y") : 0, + Width = ext != null ? (double)ext.Attribute("cx") : 0, + Height = ext != null ? (double)ext.Attribute("cy") : 0, + }; + } + + private SmfRichText ParseRichText(XElement? txBody) + { + var rt = new SmfRichText(); + if (txBody == null) return rt; + + var bodyPr = txBody.Element(A + "bodyPr"); + if (bodyPr != null) + { + rt.LeftMargin = (double?)bodyPr.Attribute("lIns") ?? 91440; + rt.RightMargin = (double?)bodyPr.Attribute("rIns") ?? 91440; + rt.TopMargin = (double?)bodyPr.Attribute("tIns") ?? 91440; + rt.BottomMargin = (double?)bodyPr.Attribute("bIns") ?? 91440; + rt.WordWrap = (string)bodyPr.Attribute("wrap") != "none"; + rt.AutoFit = bodyPr.Element(A + "normAutofit") != null; + + var anchor = (string)bodyPr.Attribute("anchor") ?? "t"; + rt.VerticalAlignment = anchor switch + { + "t" => SmfVerticalAlignment.Top, + "m" => SmfVerticalAlignment.Middle, + "b" => SmfVerticalAlignment.Bottom, + _ => SmfVerticalAlignment.Top, + }; + } + + foreach (var para in txBody.Elements(A + "p")) + { + var pElem = new SmfParagraph(); + var pPr = para.Element(A + "pPr"); + if (pPr != null) + { + var algn = (string)pPr.Attribute("algn"); + pElem.Alignment = algn switch + { + "l" => SmfHorizontalAlignment.Left, + "ctr" => SmfHorizontalAlignment.Center, + "r" => SmfHorizontalAlignment.Right, + "just" => SmfHorizontalAlignment.Justify, + _ => null, + }; + pElem.SpaceBefore = (double?)pPr.Attribute("spcBef"); + pElem.SpaceAfter = (double?)pPr.Attribute("spcAft"); + var lnSpc = pPr.Element(A + "lnSpc"); + if (lnSpc != null) + { + var spcPct = lnSpc.Element(A + "spcPct"); + if (spcPct != null) + pElem.LineSpacing = (double)spcPct.Attribute("val") / 100000.0; + } + } + + foreach (var run in para.Elements(A + "r")) + { + var rElem = new SmfRun + { + Text = run.Element(A + "t")?.Value ?? "", + Formatting = ParseFormatting(run.Element(A + "rPr")), + }; + pElem.Runs.Add(rElem); + } + + if (pElem.Runs.Count == 0) + { + var br = para.Element(A + "br"); + if (br != null) + pElem.Runs.Add(new SmfRun { Text = "\n" }); + } + + if (pElem.Runs.Count > 0 || para.Elements().Any()) + rt.Paragraphs.Add(pElem); + } + + return rt; + } + + private SmfFormatting? ParseFormatting(XElement? rPr) + { + if (rPr == null) return null; + var fmt = new SmfFormatting(); + + var sz = rPr.Attribute("sz"); + if (sz != null) fmt.Size = (double)sz / 100.0; + + var b = rPr.Attribute("b"); + if (b != null) fmt.Bold = (string)b == "1" || (string)b == "true"; + + var i = rPr.Attribute("i"); + if (i != null) fmt.Italic = (string)i == "1" || (string)i == "true"; + + var u = rPr.Attribute("u"); + if (u != null) fmt.Underline = (string)u != "none"; + + var strike = rPr.Attribute("strike"); + if (strike != null) fmt.Strikethrough = (string)strike == "sngStrike" || (string)strike == "dblStrike"; + + var solidFill = rPr.Element(A + "solidFill"); + if (solidFill != null) fmt.Color = ParseColor(solidFill); + + var latin = rPr.Element(A + "latin"); + if (latin != null) fmt.Font = (string)latin.Attribute("typeface"); + + var ea = rPr.Element(A + "ea"); + if (ea != null) fmt.Font ??= (string)ea.Attribute("typeface"); + + var hlinkClick = rPr.Element(A + "hlinkClick"); + if (hlinkClick != null) fmt.LinkTarget = (string)hlinkClick.Attribute(R + "id"); + + if (fmt.Size == null && fmt.Bold == null && fmt.Italic == null && + fmt.Underline == null && fmt.Color == null && fmt.Font == null) + return null; + + return fmt; + } + + private SmfColor? ParseColor(XElement? parent) + { + if (parent == null) return null; + var srgbClr = parent.Element(A + "srgbClr"); + if (srgbClr != null) + { + var alpha = srgbClr.Element(A + "alpha"); + return new SmfColor + { + Value = (string)srgbClr.Attribute("val") ?? "000000", + Alpha = alpha != null ? (double)alpha.Attribute("val") / 1000.0 : 100, + }; + } + var schemeClr = parent.Element(A + "schemeClr"); + if (schemeClr != null) + { + var val = (string)schemeClr.Attribute("val") ?? "dk1"; + return new SmfColor { Value = SchemeToRgb(val), Alpha = 100 }; + } + var prstClr = parent.Element(A + "prstClr"); + if (prstClr != null) + { + return new SmfColor { Value = (string)prstClr.Attribute("val") ?? "000000", Alpha = 100 }; + } + return null; + } + + private static string SchemeToRgb(string scheme) + { + return scheme switch + { + "dk1" => "000000", + "lt1" => "FFFFFF", + "dk2" => "1F1F1F", + "lt2" => "FFFFFF", + "accent1" => "4472C4", + "accent2" => "ED7D31", + "accent3" => "A5A5A5", + "accent4" => "FFC000", + "accent5" => "5B9BD5", + "accent6" => "70AD47", + "hlink" => "0563C1", + "folHlink" => "954F72", + _ => "000000", + }; + } + + private SmfFill ParseFill(XElement? spPr) + { + if (spPr == null) return new SmfFill { Type = SmfFillType.None }; + var fill = new SmfFill(); + + var noFill = spPr.Element(A + "noFill"); + if (noFill != null) { fill.Type = SmfFillType.None; return fill; } + + var solidFill = spPr.Element(A + "solidFill"); + if (solidFill != null) + { + fill.Type = SmfFillType.Solid; + fill.SolidColor = ParseColor(solidFill); + return fill; + } + + var gradFill = spPr.Element(A + "gradFill"); + if (gradFill != null) + { + fill.Type = SmfFillType.Gradient; + fill.Gradient = ParseGradient(gradFill); + return fill; + } + + var blipFill = spPr.Element(A + "blipFill"); + if (blipFill != null) + { + fill.Type = SmfFillType.Solid; + fill.SolidColor = new SmfColor { Value = "D9E2F3", Alpha = 100 }; + } + + return fill; + } + + private SmfGradientFill? ParseGradient(XElement gradFill) + { + var g = new SmfGradientFill(); + var gsLst = gradFill.Element(A + "gsLst"); + if (gsLst != null) + { + foreach (var gs in gsLst.Elements(A + "gs")) + { + var pos = (double)gs.Attribute("pos") / 1000.0; + var color = ParseColor(gs); + if (color != null) + g.Stops.Add(new SmfGradientStop { Position = pos, Color = color }); + } + } + + var lin = gradFill.Element(A + "lin"); + if (lin != null) + { + g.Type = SmfGradientType.Linear; + g.Angle = (double)lin.Attribute("ang") / 60000.0; + } + + var path = gradFill.Element(A + "path"); + if (path != null) + { + var pathType = (string)path.Attribute("path") ?? "circle"; + g.Type = pathType switch + { + "rect" => SmfGradientType.Rectangular, + "shape" => SmfGradientType.Path, + _ => SmfGradientType.Radial, + }; + } + + return g; + } + + private SmfStroke? ParseStroke(XElement? spPr) + { + if (spPr == null) return null; + var ln = spPr.Element(A + "ln"); + if (ln == null) return null; + + var stroke = new SmfStroke + { + Width = (double?)ln.Attribute("w") ?? 1, + Color = ParseColor(ln), + }; + + var prstDash = ln.Element(A + "prstDash"); + if (prstDash != null) + { + stroke.DashStyle = (string)prstDash.Attribute("val") switch + { + "dash" => SmfStrokeDashStyle.Dash, + "dot" => SmfStrokeDashStyle.Dot, + "dashDot" => SmfStrokeDashStyle.DashDot, + "lgDash" => SmfStrokeDashStyle.LongDash, + "lgDashDot" => SmfStrokeDashStyle.LongDashDot, + "dashDotDot" => SmfStrokeDashStyle.DashDotDot, + _ => SmfStrokeDashStyle.Solid, + }; + } + + return stroke; + } + + private SmfTableBorder ParseTableBorder(XElement ln) + { + var border = new SmfTableBorder + { + Width = (double?)ln.Attribute("w") ?? 6350, + }; + var solidFill = ln.Element(A + "solidFill"); + if (solidFill != null) + { + var srgb = solidFill.Element(A + "srgbClr"); + if (srgb != null) + border.Color = (string)srgb.Attribute("val") ?? "000000"; + } + var prstDash = ln.Element(A + "prstDash"); + if (prstDash != null) + { + border.Style = (string)prstDash.Attribute("val") switch + { + "solid" => SmfTableBorderStyle.Solid, + "dash" => SmfTableBorderStyle.Dashed, + "dot" => SmfTableBorderStyle.Dotted, + _ => SmfTableBorderStyle.Solid, + }; + } + else + { + border.Style = SmfTableBorderStyle.Solid; + } + return border; + } + + private static SmfShapeType ParseShapeType(string prst) + { + return prst switch + { + "rect" or "roundRect" => SmfShapeType.Rectangle, + "ellipse" => SmfShapeType.Ellipse, + "triangle" => SmfShapeType.Triangle, + "rtTriangle" => SmfShapeType.RightTriangle, + "diamond" => SmfShapeType.Diamond, + "parallelogram" => SmfShapeType.Parallelogram, + "trapezoid" => SmfShapeType.Trapezoid, + "pentagon" => SmfShapeType.Pentagon, + "hexagon" => SmfShapeType.Hexagon, + "octagon" => SmfShapeType.Octagon, + "star5" => SmfShapeType.Star, + "arrow" or "arrowRight" => SmfShapeType.ArrowRight, + "arrowLeft" => SmfShapeType.ArrowLeft, + "arrowUp" => SmfShapeType.ArrowUp, + "arrowDown" => SmfShapeType.ArrowDown, + "leftArrow" => SmfShapeType.ArrowLeft, + "upArrow" => SmfShapeType.ArrowUp, + "downArrow" => SmfShapeType.ArrowDown, + "line" => SmfShapeType.Line, + "straightConnector1" => SmfShapeType.Line, + "bentConnector2" => SmfShapeType.LineArrow, + "cloud" => SmfShapeType.Cloud, + "heart" => SmfShapeType.Heart, + "cross" => SmfShapeType.Cross, + "chevron" => SmfShapeType.Chevron, + "pie" => SmfShapeType.Pie, + "blockArc" => SmfShapeType.BlockArc, + "roundedRect" => SmfShapeType.RoundedRectangle, + _ => SmfShapeType.Rectangle, + }; + } + + private static string guessContentType(string fileName) + { + var ext = Path.GetExtension(fileName).ToLower(); + return ext switch + { + ".png" => "image/png", + ".jpg" or ".jpeg" => "image/jpeg", + ".gif" => "image/gif", + ".bmp" => "image/bmp", + ".svg" => "image/svg+xml", + ".webp" => "image/webp", + ".mp4" => "video/mp4", + ".avi" => "video/avi", + ".wmv" => "video/x-ms-wmv", + ".mp3" => "audio/mpeg", + ".wav" => "audio/wav", + ".wmf" => "image/x-wmf", + ".emf" => "image/x-emf", + _ => "application/octet-stream", + }; + } + + private string NextId() => $"elem_{_nextId++}"; + + private static XDocument GetPresentation(Package pptx) + { + var relsUri = PackUriHelper.CreatePartUri(new Uri("/_rels/.rels", UriKind.Relative)); + var relsPart = pptx.GetPart(relsUri); + var rels = XDocument.Load(relsPart.GetStream()); + var officeRel = rels.Root?.Elements("Relationship") + .FirstOrDefault(r => (string)r.Attribute("Type") == + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"); + if (officeRel == null) throw new InvalidDataException("未找到 Office Document 关系"); + var presUri = PackUriHelper.CreatePartUri(new Uri((string)officeRel.Attribute("Target"), UriKind.Relative)); + var presPart = pptx.GetPart(presUri); + return XDocument.Load(presPart.GetStream()); + } + + private static Tuple GetSlideSize(XDocument presentation) + { + var sldSz = presentation.Root?.Element(P + "sldSz"); + if (sldSz == null) return Tuple.Create(12192000.0, 6858000.0); + return Tuple.Create( + (double)sldSz.Attribute("cx"), + (double)sldSz.Attribute("cy")); + } + + private static string? GetProperty(XDocument pres, XName name) + { + var val = pres.Root?.Descendants().FirstOrDefault(d => d.Name == name); + return val?.Value; + } + + private static List<(string RelId, string Target, int SlideNum)> GetSlideRelations(Package pptx, XDocument presentation) + { + var relsUri = PackUriHelper.CreatePartUri(new Uri("/ppt/_rels/presentation.xml.rels", UriKind.Relative)); + var relsPart = pptx.GetPart(relsUri); + var rels = XDocument.Load(relsPart.GetStream()); + var slideType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide"; + var result = new List<(string, string, int)>(); + int num = 0; + var sldIdLst = presentation.Root?.Element(P + "sldIdLst"); + var order = new List(); + if (sldIdLst != null) + { + foreach (var sldId in sldIdLst.Elements(P + "sldId")) + { + var rId = (string)sldId.Attribute(R + "id"); + order.Add(rId); + } + } + + var relDict = rels.Root?.Elements("Relationship") + .Where(r => (string)r.Attribute("Type") == slideType) + .ToDictionary(r => (string)r.Attribute("Id"), r => (string)r.Attribute("Target")); + + if (relDict != null) + { + foreach (var rId in order) + { + if (relDict.TryGetValue(rId, out var target)) + { + num++; + result.Add((rId, target, num)); + } + } + foreach (var (id, target) in relDict) + { + if (!order.Contains(id)) + { + num++; + result.Add((id, target, num)); + } + } + } + + return result; + } + } +} diff --git a/Better-EN5/Services/Conversion/SmfTypes.cs b/Better-EN5/Services/Conversion/SmfTypes.cs index 314501a..55964e7 100644 --- a/Better-EN5/Services/Conversion/SmfTypes.cs +++ b/Better-EN5/Services/Conversion/SmfTypes.cs @@ -37,7 +37,7 @@ namespace BetterEN5.Services.Conversion public enum SmfFillType { - None, Solid, Gradient, Pattern, SlideBackground + None, Solid, Gradient, Pattern, Image, SlideBackground } public enum SmfGradientType diff --git a/Better-EN5/Services/CustomConversionService.cs b/Better-EN5/Services/CustomConversionService.cs index 8b0c826..4a90a39 100644 --- a/Better-EN5/Services/CustomConversionService.cs +++ b/Better-EN5/Services/CustomConversionService.cs @@ -1,539 +1,60 @@ using System; -using System.Collections.Generic; using System.IO; -using System.IO.Compression; -using System.Linq; using System.Threading.Tasks; -using System.Xml.Linq; +using BetterEN5.Services.Conversion; namespace BetterEN5.Services { public class CustomConversionService : IConversionService { - private static readonly XNamespace A = "http://schemas.openxmlformats.org/drawingml/2006/main"; - private static readonly XNamespace P = "http://schemas.openxmlformats.org/presentationml/2006/main"; - private static readonly XNamespace R = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"; - private static readonly XNamespace Rel = "http://schemas.openxmlformats.org/package/2006/relationships"; + private readonly ConversionEngine _engine = new(); public async Task ConvertPptxToEnbxAsync(string pptxPath) { - if (!File.Exists(pptxPath)) - throw new FileNotFoundException("PPT 文件未找到", pptxPath); - - return await Task.Run(() => - { - var enbxPath = Path.ChangeExtension(pptxPath, ".enbx"); - using var pptx = System.IO.Packaging.Package.Open(pptxPath, FileMode.Open, FileAccess.Read); - using var enbx = System.IO.Packaging.Package.Open(enbxPath, FileMode.Create, FileAccess.ReadWrite); - - var pptxPresentation = GetPptxPresentation(pptx); - var pptxSlideSize = GetSlideSize(pptxPresentation); - var slideWidth = pptxSlideSize.Item1; - var slideHeight = pptxSlideSize.Item2; - - var enbxDoc = new XDocument(); - var board = new XElement("DocumentStorageModel"); - - var slideIds = new List(); - var slidesElement = new XElement("Slides"); - - var slideRels = GetPresentationSlideRels(pptx, pptxPresentation); - for (int i = 0; i < slideRels.Count; i++) - { - var slideId = $"slide_{i + 1}"; - slideIds.Add(slideId); - - var slidePart = pptx.GetPart( - System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri(slideRels[i].Item2, UriKind.Relative))); - var slideXml = XDocument.Load(slidePart.GetStream()); - - var slideElement = new XElement("SlideSaveInfo", - new XElement("Id", slideId), - new XElement("Width", slideWidth), - new XElement("Height", slideHeight)); - - var elementsElement = new XElement("Elements"); - var spTree = slideXml.Root?.Element(P + "cSld")?.Element(P + "spTree"); - if (spTree != null) - { - int elemIdx = 0; - foreach (var sp in spTree.Elements(P + "sp")) - { - var xfrm = sp.Element(P + "spPr")?.Element(A + "xfrm"); - if (xfrm == null) continue; - - var off = xfrm.Element(A + "off"); - var ext = xfrm.Element(A + "ext"); - if (off == null || ext == null) continue; - - var x = (double)off.Attribute("x"); - var y = (double)off.Attribute("y"); - var w = (double)ext.Attribute("cx"); - var h = (double)ext.Attribute("cy"); - - var nvSpPr = sp.Element(P + "nvSpPr"); - var cNvSpPr = nvSpPr?.Element(P + "cNvSpPr"); - var isTextBox = cNvSpPr?.Attribute("txBox") != null; - var txBody = sp.Element(P + "txBody"); - var isTextShape = txBody != null; - - if (isTextBox || isTextShape) - { - var elem = new XElement("TextSaveInfo", - new XElement("Id", $"elem_{++elemIdx}"), - new XElement("X", x), - new XElement("Y", y), - new XElement("Width", w), - new XElement("Height", h), - new XElement("Rotation", 0.0), - new XElement("Locked", false)); - - var bodyPr = txBody?.Element(A + "bodyPr"); - var lIns = (double?)bodyPr?.Attribute("lIns") ?? 91440; - var rIns = (double?)bodyPr?.Attribute("rIns") ?? 91440; - var tIns = (double?)bodyPr?.Attribute("tIns") ?? 91440; - var bIns = (double?)bodyPr?.Attribute("bIns") ?? 91440; - - var richText = new XElement("RichText", - new XElement("LeftMargin", lIns), - new XElement("RightMargin", rIns), - new XElement("TopMargin", tIns), - new XElement("BottomMargin", bIns)); - - if (txBody != null) - { - foreach (var para in txBody.Elements(A + "p")) - { - var pElem = new XElement("Paragraph"); - foreach (var run in para.Elements(A + "r")) - { - var rPr = run.Element(A + "rPr"); - var text = run.Element(A + "t")?.Value ?? ""; - - var rElem = new XElement("Run", - new XElement("Text", text)); - if (rPr != null) - { - var fmt = new XElement("Formatting"); - var sz = rPr.Attribute("sz"); - if (sz != null) fmt.Add(new XElement("Size", (double)sz / 100.0)); - var b = rPr.Attribute("b"); - if (b != null) fmt.Add(new XElement("Bold", (string)b == "1")); - var italicAttr = rPr.Attribute("i"); - if (italicAttr != null) fmt.Add(new XElement("Italic", (string)italicAttr == "1")); - var u = rPr.Attribute("u"); - if (u != null) fmt.Add(new XElement("Underline", u.Value)); - var srgbClr = rPr?.Element(A + "solidFill")?.Element(A + "srgbClr"); - if (srgbClr != null) fmt.Add(new XElement("Color", (string)srgbClr.Attribute("val"))); - var latin = rPr?.Element(A + "latin"); - if (latin != null) fmt.Add(new XElement("Font", (string)latin.Attribute("typeface"))); - if (fmt.HasElements) rElem.Add(fmt); - } - pElem.Add(rElem); - } - richText.Add(pElem); - } - } - - elem.Add(richText); - elementsElement.Add(elem); - } - } - } - - slideElement.Add(elementsElement); - slidesElement.Add(slideElement); - } - - board.Add(new XElement("Board", - new XElement("SlideWidth", slideWidth), - new XElement("SlideHeight", slideHeight), - new XElement("SlideIds", - slideIds.Select(id => new XElement("string", id))))); - board.Add(slidesElement); - enbxDoc.Add(board); - - var enbxPart = enbx.CreatePart( - System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/document.xml", UriKind.Relative)), - "application/xml"); - using (var stream = enbxPart.GetStream()) - enbxDoc.Save(stream); - - enbx.Close(); - pptx.Close(); - - return enbxPath; - }); + return await _engine.ConvertPptxToEnbxAsync(pptxPath); } public async Task ConvertEnbxToPptxAsync(string enbxPath) { - if (!File.Exists(enbxPath)) - throw new FileNotFoundException("ENBX 文件未找到", enbxPath); - - return await Task.Run(() => - { - var pptxPath = Path.ChangeExtension(enbxPath, ".pptx"); - - using var enbx = System.IO.Packaging.Package.Open(enbxPath, FileMode.Open, FileAccess.Read); - using var pptx = System.IO.Packaging.Package.Open(pptxPath, FileMode.Create, FileAccess.ReadWrite); - - var enbxDocUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/document.xml", UriKind.Relative)); - var enbxPart = enbx.GetPart(enbxDocUri); - var enbxDoc = XDocument.Load(enbxPart.GetStream()); - - var root = enbxDoc.Root; - if (root == null) throw new InvalidDataException("无效的 ENBX 文件"); - - var board = root.Element("Board"); - var slideWidth = (double?)board?.Element("SlideWidth") ?? 12192000; - var slideHeight = (double?)board?.Element("SlideHeight") ?? 6858000; - - var slidesEl = root.Element("Slides"); - var slides = slidesEl?.Elements("SlideSaveInfo").ToList() ?? new List(); - - var contentTypesUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/[Content_Types].xml", UriKind.Relative)); - var ctPart = pptx.CreatePart(contentTypesUri, "application/xml"); - using (var sw = new StreamWriter(ctPart.GetStream())) - { - sw.Write(""); - sw.Write(""); - sw.Write(""); - sw.Write(""); - sw.Write(""); - sw.Write(""); - sw.Write(""); - sw.Write(""); - for (int i = 0; i < slides.Count; i++) - sw.Write($""); - sw.Write(""); - sw.Write(""); - sw.Write(""); - sw.Write(""); - } - - var pptxPresentation = new XDocument(new XElement(P + "presentation", - new XAttribute(XNamespace.Xmlns + "a", A.NamespaceName), - new XAttribute(XNamespace.Xmlns + "r", R.NamespaceName), - new XAttribute(XNamespace.Xmlns + "p", P.NamespaceName), - new XElement(P + "sldMasterIdLst", - new XElement(P + "sldMasterId", new XAttribute("id", 2147483648), new XAttribute(R + "id", "rId1"))), - new XElement(P + "sldIdLst"), - new XElement(P + "sldSz", new XAttribute("cx", slideWidth), new XAttribute("cy", slideHeight)), - new XElement(P + "notesSz", new XAttribute("cx", 6858000), new XAttribute("cy", 9144000)))); - - var presXmlUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/ppt/presentation.xml", UriKind.Relative)); - var presPart = pptx.CreatePart(presXmlUri, "application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml"); - var presRelUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/ppt/_rels/presentation.xml.rels", UriKind.Relative)); - var presRelPart = pptx.CreatePart(presRelUri, "application/vnd.openxmlformats-package.relationships+xml"); - - var presRelNs = XNamespace.Get(Rel.NamespaceName); - var presRels = new XDocument(new XElement(presRelNs + "Relationships", - new XElement(presRelNs + "Relationship", new XAttribute("Id", "rId1"), - new XAttribute("Type", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster"), - new XAttribute("Target", "slideMasters/slideMaster1.xml")))); - - var slideIdLst = pptxPresentation.Root?.Element(P + "sldIdLst"); - var nextRelId = 2; - var slideRelTargets = new List(); - - for (int i = 0; i < slides.Count; i++) - { - var slideId = $"slide_{i + 1}"; - var slideFileName = $"slides/slide{i + 1}.xml"; - slideRelTargets.Add(slideFileName); - - slideIdLst?.Add(new XElement(P + "sldId", - new XAttribute("id", 256 + i), - new XAttribute(R + "id", $"rId{nextRelId + i}"))); - - presRels.Root?.Add(new XElement("Relationship", - new XAttribute("Id", $"rId{nextRelId + i}"), - new XAttribute("Type", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide"), - new XAttribute("Target", slideFileName))); - - var slideDoc = new XDocument(new XElement(P + "sld", - new XAttribute(XNamespace.Xmlns + "a", A.NamespaceName), - new XAttribute(XNamespace.Xmlns + "r", R.NamespaceName), - new XAttribute(XNamespace.Xmlns + "p", P.NamespaceName))); - - var cSld = new XElement(P + "cSld"); - var spTree = new XElement(P + "spTree"); - - spTree.Add(new XElement(P + "nvGrpSpPr", - new XElement(P + "cNvPr", new XAttribute("id", 1), new XAttribute("name", "")), - new XElement(P + "cNvGrpSpPr"))); - spTree.Add(new XElement(P + "grpSpPr", - new XElement(A + "xfrm", - new XElement(A + "off", new XAttribute("x", 0), new XAttribute("y", 0)), - new XElement(A + "ext", new XAttribute("cx", 0), new XAttribute("cy", 0)), - new XElement(A + "chOff", new XAttribute("x", 0), new XAttribute("y", 0)), - new XElement(A + "chExt", new XAttribute("cx", 0), new XAttribute("cy", 0))))); - - var elements = slides[i].Element("Elements"); - if (elements != null) - { - int shapeId = 2; - foreach (var elem in elements.Elements()) - { - var elemName = elem.Name.LocalName; - var x = (double)elem.Element("X"); - var y = (double)elem.Element("Y"); - var w = (double)elem.Element("Width"); - var h = (double)elem.Element("Height"); - var rotation = (double?)elem.Element("Rotation") ?? 0.0; - - if (elemName == "TextSaveInfo") - { - var sp = new XElement(P + "sp"); - sp.Add(new XElement(P + "nvSpPr", - new XElement(P + "cNvPr", new XAttribute("id", shapeId++), new XAttribute("name", $"TextBox{shapeId - 1}")), - new XElement(P + "cNvSpPr", new XAttribute("txBox", 1)), - new XElement(P + "nvPr"))); - - var spPr = new XElement(P + "spPr", - new XElement(A + "xfrm", - new XElement(A + "off", new XAttribute("x", x), new XAttribute("y", y)), - new XElement(A + "ext", new XAttribute("cx", w), new XAttribute("cy", h))), - new XElement(A + "prstGeom", new XAttribute("prst", "rect"), - new XElement(A + "avLst"))); - sp.Add(spPr); - - var txBody = new XElement(P + "txBody", - new XElement(A + "bodyPr", - new XAttribute("wrap", "square"))); - - var richText = elem.Element("RichText"); - if (richText != null) - { - var leftMargin = (double?)richText.Element("LeftMargin") ?? 91440; - var rightMargin = (double?)richText.Element("RightMargin") ?? 91440; - var topMargin = (double?)richText.Element("TopMargin") ?? 91440; - var bottomMargin = (double?)richText.Element("BottomMargin") ?? 91440; - - var bodyPr = txBody.Element(A + "bodyPr"); - if (bodyPr != null) - { - bodyPr.Add(new XAttribute("lIns", leftMargin)); - bodyPr.Add(new XAttribute("rIns", rightMargin)); - bodyPr.Add(new XAttribute("tIns", topMargin)); - bodyPr.Add(new XAttribute("bIns", bottomMargin)); - } - - var paragraphs = richText.Elements("Paragraph"); - if (!paragraphs.Any()) - { - txBody.Add(new XElement(A + "p")); - } - else - { - foreach (var para in paragraphs) - { - var pElem = new XElement(A + "p"); - var runs = para.Elements("Run"); - if (!runs.Any()) - { - pElem.Add(new XElement(A + "endParaRPr")); - } - else - { - foreach (var run in runs) - { - var text = (string)run.Element("Text") ?? ""; - var rElem = new XElement(A + "r"); - var fmt = run.Element("Formatting"); - if (fmt != null) - { - var rPr = new XElement(A + "rPr"); - var sz = fmt.Element("Size"); - if (sz != null) rPr.Add(new XAttribute("sz", (double)sz * 100)); - var bold = fmt.Element("Bold"); - if (bold != null && (bool)bold) rPr.Add(new XAttribute("b", 1)); - var italic = fmt.Element("Italic"); - if (italic != null && (bool)italic) rPr.Add(new XAttribute("i", 1)); - var underline = (string?)fmt.Element("Underline"); - if (underline != null) rPr.Add(new XAttribute("u", underline)); - var color = (string?)fmt.Element("Color"); - if (color != null) - rPr.Add(new XElement(A + "solidFill", new XElement(A + "srgbClr", new XAttribute("val", color)))); - var font = (string?)fmt.Element("Font"); - if (font != null) - rPr.Add(new XElement(A + "latin", new XAttribute("typeface", font))); - if (rPr.HasAttributes || rPr.HasElements) rElem.Add(rPr); - } - rElem.Add(new XElement(A + "t", text)); - pElem.Add(rElem); - } - } - txBody.Add(pElem); - } - } - } - else - { - txBody.Add(new XElement(A + "p")); - } - - sp.Add(txBody); - spTree.Add(sp); - } - } - } - - cSld.Add(spTree); - slideDoc.Root?.Add(cSld); - - var slideUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri($"/ppt/{slideFileName}", UriKind.Relative)); - var slidePart = pptx.CreatePart(slideUri, "application/vnd.openxmlformats-officedocument.presentationml.slide+xml"); - using (var sw = new StreamWriter(slidePart.GetStream())) - slideDoc.Save(sw); - - var slideRelUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri($"/ppt/slides/_rels/slide{i + 1}.xml.rels", UriKind.Relative)); - var slideRelPart = pptx.CreatePart(slideRelUri, "application/vnd.openxmlformats-package.relationships+xml"); - using (var sw = new StreamWriter(slideRelPart.GetStream())) - { - sw.Write(""); - sw.Write(""); - sw.Write(""); - } - } - - var rootRelUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/_rels/.rels", UriKind.Relative)); - var rootRelPart = pptx.CreatePart(rootRelUri, "application/vnd.openxmlformats-package.relationships+xml"); - using (var sw = new StreamWriter(rootRelPart.GetStream())) - { - sw.Write(""); - sw.Write(""); - sw.Write(""); - } - - using (var sw = new StreamWriter(presPart.GetStream())) - pptxPresentation.Save(sw); - using (var sw = new StreamWriter(presRelPart.GetStream())) - presRels.Save(sw); - - CreateMinimalTheme(pptx); - CreateMinimalSlideMaster(pptx); - CreateMinimalSlideLayout(pptx); - CreatePresProps(pptx); - CreateViewProps(pptx); - - enbx.Close(); - pptx.Close(); - - return pptxPath; - }); + return await _engine.ConvertEnbxToPptxAsync(enbxPath); } public Task FixConversionErrorsAsync() { - var en5Path = Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), - "Seewo", "EasiNote5"); - var tempDir = Path.Combine(en5Path, "Temp", "Conversion"); - if (Directory.Exists(tempDir)) - { - try { Directory.Delete(tempDir, true); } catch { } - } + _engine.FixConversionErrors(); return Task.CompletedTask; } - private static Tuple GetSlideSize(XDocument presentation) + public async Task BatchConvertAsync(string[] inputPaths, string outputDir, bool toEnbx) { - var sldSz = presentation.Root?.Element(P + "sldSz"); - if (sldSz == null) return Tuple.Create(12192000.0, 6858000.0); - return Tuple.Create( - (double)sldSz.Attribute("cx"), - (double)sldSz.Attribute("cy")); - } + int success = 0; + int failed = 0; - private static XDocument GetPptxPresentation(System.IO.Packaging.Package pptx) - { - var relsUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/_rels/.rels", UriKind.Relative)); - var relsPart = pptx.GetPart(relsUri); - var rels = XDocument.Load(relsPart.GetStream()); - var officeRel = rels.Root?.Elements("Relationship") - .FirstOrDefault(r => (string)r.Attribute("Type") == - "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"); - if (officeRel == null) throw new InvalidDataException("未找到 Office Document 关系"); - var presUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri((string)officeRel.Attribute("Target"), UriKind.Relative)); - var presPart = pptx.GetPart(presUri); - return XDocument.Load(presPart.GetStream()); - } + if (!Directory.Exists(outputDir)) + Directory.CreateDirectory(outputDir); - private static List> GetPresentationSlideRels(System.IO.Packaging.Package pptx, XDocument presentation) - { - var relsUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/ppt/_rels/presentation.xml.rels", UriKind.Relative)); - var relsPart = pptx.GetPart(relsUri); - var rels = XDocument.Load(relsPart.GetStream()); - var slideType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide"; - return rels.Root?.Elements("Relationship") - .Where(r => (string)r.Attribute("Type") == slideType) - .Select(r => Tuple.Create((string)r.Attribute("Id"), (string)r.Attribute("Target"))) - .ToList() ?? new List>(); - } - - private static void CreateMinimalTheme(System.IO.Packaging.Package pptx) - { - var themeUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/ppt/theme/theme1.xml", UriKind.Relative)); - var themePart = pptx.CreatePart(themeUri, "application/vnd.openxmlformats-officedocument.theme+xml"); - using (var sw = new StreamWriter(themePart.GetStream())) + foreach (var input in inputPaths) { - sw.Write(""); - sw.Write(""); - sw.Write(""); - sw.Write(""); - sw.Write(""); - sw.Write(""); - } - } + try + { + var fileName = Path.GetFileNameWithoutExtension(input); + var ext = toEnbx ? ".enbx" : ".pptx"; + var output = Path.Combine(outputDir, fileName + ext); - private static void CreateMinimalSlideMaster(System.IO.Packaging.Package pptx) - { - var masterUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/ppt/slideMasters/slideMaster1.xml", UriKind.Relative)); - var masterPart = pptx.CreatePart(masterUri, "application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml"); - using (var sw = new StreamWriter(masterPart.GetStream())) - { - sw.Write(""); - sw.Write(""); - sw.Write(""); + if (toEnbx) + await _engine.ConvertPptxToEnbxAsync(input); + else + await _engine.ConvertEnbxToPptxAsync(input); + + success++; + } + catch + { + failed++; + } } - var masterRelUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/ppt/slideMasters/_rels/slideMaster1.xml.rels", UriKind.Relative)); - var masterRelPart = pptx.CreatePart(masterRelUri, "application/vnd.openxmlformats-package.relationships+xml"); - using (var sw = new StreamWriter(masterRelPart.GetStream())) - { - sw.Write(""); - sw.Write(""); - sw.Write(""); - } - } - - private static void CreateMinimalSlideLayout(System.IO.Packaging.Package pptx) - { - var layoutUri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/ppt/slideLayouts/slideLayout1.xml", UriKind.Relative)); - var layoutPart = pptx.CreatePart(layoutUri, "application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml"); - using (var sw = new StreamWriter(layoutPart.GetStream())) - { - sw.Write(""); - sw.Write(""); - } - } - - private static void CreatePresProps(System.IO.Packaging.Package pptx) - { - var uri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/ppt/presProps.xml", UriKind.Relative)); - var part = pptx.CreatePart(uri, "application/vnd.openxmlformats-officedocument.presentationml.presProps+xml"); - using (var sw = new StreamWriter(part.GetStream())) - sw.Write(""); - } - - private static void CreateViewProps(System.IO.Packaging.Package pptx) - { - var uri = System.IO.Packaging.PackUriHelper.CreatePartUri(new Uri("/ppt/viewProps.xml", UriKind.Relative)); - var part = pptx.CreatePart(uri, "application/vnd.openxmlformats-officedocument.presentationml.viewProps+xml"); - using (var sw = new StreamWriter(part.GetStream())) - sw.Write(""); + return $"批量转换完成,成功: {success},失败: {failed}"; } } } diff --git a/Manager/MainWindow.xaml b/Manager/MainWindow.xaml index 44c5899..ba1ae77 100644 --- a/Manager/MainWindow.xaml +++ b/Manager/MainWindow.xaml @@ -168,16 +168,48 @@ Foreground="#60B0FF" Margin="0,0,0,6"/> - - - - - - -