diff --git a/Better-EN5/Services/Conversion/ConversionEngine.cs b/Better-EN5/Services/Conversion/ConversionEngine.cs new file mode 100644 index 0000000..e59fa1f --- /dev/null +++ b/Better-EN5/Services/Conversion/ConversionEngine.cs @@ -0,0 +1,24 @@ +using System; + +namespace BetterEN5.Services.Conversion; + +public class ConversionEngine +{ + private readonly PptxImporter _importer = new(); + private readonly EnbxWriter _writer = new(); + + public bool Convert(string pptxPath, string enbxPath) + { + try + { + var doc = _importer.Import(pptxPath); + _writer.Write(doc, enbxPath); + return true; + } + catch (Exception ex) + { + Console.Error.WriteLine($"Conversion failed: {ex}"); + return false; + } + } +} diff --git a/Better-EN5/Services/Conversion/DocumentModel.cs b/Better-EN5/Services/Conversion/DocumentModel.cs new file mode 100644 index 0000000..9030ec2 --- /dev/null +++ b/Better-EN5/Services/Conversion/DocumentModel.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; + +namespace BetterEN5.Services.Conversion; + +public class DocumentModel +{ + public string Title { get; set; } = ""; + public string Author { get; set; } = ""; + public long SlideWidth { get; set; } = 12192000; + public long SlideHeight { get; set; } = 6858000; + public List Slides { get; set; } = new(); +} + +public class SlideModel +{ + public string Name { get; set; } = ""; + public long Width { get; set; } + public long Height { get; set; } + public List Elements { get; set; } = new(); +} + +public abstract class ElementModel +{ +} + +public class TextElement : ElementModel +{ + public string Text { get; set; } = ""; + public long X { get; set; } + public long Y { get; set; } + public long Width { get; set; } + public long Height { get; set; } + public string FontName { get; set; } = "微软雅黑"; + public double FontSize { get; set; } = 18; + public string FontColor { get; set; } = "#FF000000"; + public bool IsBold { get; set; } +} + +public class ImageElement : ElementModel +{ + public byte[]? Data { get; set; } + public string ContentType { get; set; } = "image/png"; + public string FileName { get; set; } = ""; + public long X { get; set; } + public long Y { get; set; } + public long Width { get; set; } + public long Height { get; set; } +} + +public class ShapeElement : ElementModel +{ + public long X { get; set; } + public long Y { get; set; } + public long Width { get; set; } + public long Height { get; set; } +} diff --git a/Better-EN5/Services/Conversion/EnbxWriter.cs b/Better-EN5/Services/Conversion/EnbxWriter.cs new file mode 100644 index 0000000..df48a53 --- /dev/null +++ b/Better-EN5/Services/Conversion/EnbxWriter.cs @@ -0,0 +1,148 @@ +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 Write(DocumentModel doc, string outputPath) + { + if (File.Exists(outputPath)) File.Delete(outputPath); + + using var package = Package.Open(outputPath, FileMode.Create); + + var docPart = package.CreatePart( + PackUriHelper.CreatePartUri(new Uri("/document.xml", UriKind.Relative)), + "application/xml"); + + var slideTargets = new List(); + int slideIndex = 0; + + foreach (var slide in doc.Slides) + { + slideIndex++; + var slideName = $"slides/slide{slideIndex}.xml"; + slideTargets.Add(slideName); + + var slidePart = package.CreatePart( + PackUriHelper.CreatePartUri(new Uri($"/{slideName}", UriKind.Relative)), + "application/xml"); + + int imgIdx = 0; + foreach (var elem in slide.Elements) + { + if (elem is ImageElement img && img.Data != null && img.Data.Length > 0) + { + imgIdx++; + var ext = Path.GetExtension(img.FileName); + if (string.IsNullOrEmpty(ext)) ext = ".png"; + var mediaName = $"media/{Guid.NewGuid():N}{ext}"; + + var imagePart = package.CreatePart( + PackUriHelper.CreatePartUri(new Uri($"/{mediaName}", UriKind.Relative)), + img.ContentType); + using (var s = imagePart.GetStream()) + s.Write(img.Data, 0, img.Data.Length); + + slidePart.CreateRelationship( + PackUriHelper.CreatePartUri(new Uri($"/{mediaName}", UriKind.Relative)), + TargetMode.Internal, + "http://schemas.seewo.com/easinote/relationships/image", + $"r{imgIdx}"); + + img.FileName = mediaName; + } + } + + WriteSlideXml(slidePart, slide); + docPart.CreateRelationship( + PackUriHelper.CreatePartUri(new Uri($"/{slideName}", UriKind.Relative)), + TargetMode.Internal, + "http://schemas.seewo.com/easinote/relationships/slide", + $"r{slideIndex}"); + } + + WriteDocumentXml(docPart, doc, slideTargets); + + package.CreateRelationship( + PackUriHelper.CreatePartUri(new Uri("/document.xml", UriKind.Relative)), + TargetMode.Internal, + "http://schemas.seewo.com/easinote/relationships/document", + "rId1"); + } + + private void WriteDocumentXml(PackagePart docPart, DocumentModel doc, List slideTargets) + { + var docXml = new XDocument( + new XElement(En + "Document", + new XAttribute(XNamespace.Xmlns + "en", En.NamespaceName), + new XAttribute(XNamespace.Xmlns + "dc", Dc.NamespaceName), + new XElement(En + "Properties", + new XElement(En + "Title", doc.Title), + new XElement(En + "Author", doc.Author), + new XElement(En + "Created", DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss")), + new XElement(En + "Application", "Better-Seewo"), + new XElement(En + "Version", "5.2.4.9855"), + new XElement(En + "SlideWidth", doc.SlideWidth), + new XElement(En + "SlideHeight", doc.SlideHeight)), + new XElement(En + "Slides", + slideTargets.Select((t, i) => + new XElement(En + "Slide", + new XAttribute("RId", $"r{i + 1}"), + new XAttribute("Source", t)))))); + + using var stream = docPart.GetStream(FileMode.Create); + docXml.Save(stream); + } + + private void WriteSlideXml(PackagePart slidePart, SlideModel slide) + { + var elems = new List(); + int imgIdx = 0; + foreach (var e in slide.Elements) + { + switch (e) + { + case TextElement t: + elems.Add(new XElement(En + "TextElement", + new XAttribute("X", t.X), + new XAttribute("Y", t.Y), + new XAttribute("Width", t.Width), + new XAttribute("Height", t.Height), + new XAttribute("FontSize", t.FontSize), + new XAttribute("IsBold", t.IsBold), + new XAttribute("FontName", t.FontName), + new XAttribute("FontColor", t.FontColor), + new XElement(En + "Text", t.Text))); + break; + case ImageElement img: + imgIdx++; + elems.Add(new XElement(En + "ImageElement", + new XAttribute("X", img.X), + new XAttribute("Y", img.Y), + new XAttribute("Width", img.Width), + new XAttribute("Height", img.Height), + new XAttribute("Source", img.FileName), + new XAttribute("RId", $"r{imgIdx}"))); + break; + } + } + + var slideXml = new XDocument( + new XElement(En + "Slide", + new XAttribute(XNamespace.Xmlns + "en", En.NamespaceName), + new XAttribute("Width", slide.Width), + new XAttribute("Height", slide.Height), + new XElement(En + "Elements", elems))); + + using var stream = slidePart.GetStream(FileMode.Create); + slideXml.Save(stream); + } +} diff --git a/Better-EN5/Services/Conversion/PptxImporter.cs b/Better-EN5/Services/Conversion/PptxImporter.cs new file mode 100644 index 0000000..89eb248 --- /dev/null +++ b/Better-EN5/Services/Conversion/PptxImporter.cs @@ -0,0 +1,142 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using DocumentFormat.OpenXml.Packaging; +using DocumentFormat.OpenXml.Presentation; +using D = DocumentFormat.OpenXml.Drawing; + +namespace BetterEN5.Services.Conversion; + +public class PptxImporter +{ + public DocumentModel Import(string pptxPath) + { + var doc = new DocumentModel(); + + using var presentation = PresentationDocument.Open(pptxPath, false); + var presPart = presentation.PresentationPart!; + var pres = presPart.Presentation; + + var slideSize = pres.SlideSize!; + doc.SlideWidth = slideSize.Cx!.Value; + doc.SlideHeight = slideSize.Cy!.Value; + + int slideIndex = 0; + foreach (var slideId in pres.SlideIdList!.ChildElements.OfType()) + { + slideIndex++; + var slidePart = presPart.GetPartById(slideId.RelationshipId!) as SlidePart; + if (slidePart == null) continue; + + var slide = slidePart.Slide; + var slideModel = new SlideModel + { + Name = $"Slide{slideIndex}", + Width = doc.SlideWidth, + Height = doc.SlideHeight + }; + + ParseSlideElements(slidePart, slide, slideModel, doc); + + doc.Slides.Add(slideModel); + } + + return doc; + } + + private void ParseSlideElements(SlidePart slidePart, Slide slide, SlideModel slideModel, DocumentModel doc) + { + if (slide.CommonSlideData?.ShapeTree == null) return; + + foreach (var element in slide.CommonSlideData.ShapeTree.ChildElements) + { + switch (element) + { + case Picture pic: + ParsePicture(slidePart, pic, slideModel); + break; + case Shape shape: + ParseShape(shape, slideModel); + break; + } + } + } + + private void ParsePicture(SlidePart slidePart, Picture pic, SlideModel slideModel) + { + var blipFill = pic.BlipFill; + if (blipFill?.Blip == null) return; + + var embed = blipFill.Blip.Embed?.Value; + if (string.IsNullOrEmpty(embed)) return; + + var imagePart = slidePart.GetPartById(embed) as ImagePart; + if (imagePart == null) return; + + using var stream = imagePart.GetStream(); + var data = new byte[stream.Length]; + stream.Read(data, 0, data.Length); + + var transform = pic.ShapeProperties?.Transform2D; + var img = new ImageElement + { + Data = data, + ContentType = imagePart.ContentType, + FileName = $"image{slideModel.Elements.Count + 1}{GetExtension(imagePart.ContentType)}", + X = transform?.Offset?.X?.Value ?? 0, + Y = transform?.Offset?.Y?.Value ?? 0, + Width = transform?.Extents?.Cx?.Value ?? 0, + Height = transform?.Extents?.Cy?.Value ?? 0 + }; + slideModel.Elements.Add(img); + } + + private void ParseShape(Shape shape, SlideModel slideModel) + { + var textBody = shape.TextBody; + if (textBody == null) return; + + var text = string.Concat( + textBody.Descendants().Select(t => t.Text)); + if (string.IsNullOrWhiteSpace(text)) return; + + var transform = shape.ShapeProperties?.Transform2D; + var textElem = new TextElement + { + Text = text.Trim(), + X = transform?.Offset?.X?.Value ?? 0, + Y = transform?.Offset?.Y?.Value ?? 0, + Width = transform?.Extents?.Cx?.Value ?? 0, + Height = transform?.Extents?.Cy?.Value ?? 0 + }; + + var firstRun = textBody.Descendants().FirstOrDefault(); + if (firstRun?.RunProperties != null) + { + var rp = firstRun.RunProperties; + textElem.FontSize = (rp.FontSize?.Value ?? 1800) / 100.0; + textElem.IsBold = rp.Bold?.Value ?? false; + + var solidFill = rp.GetFirstChild(); + if (solidFill?.RgbColorModelHex?.Val?.Value != null) + textElem.FontColor = $"#{solidFill.RgbColorModelHex.Val.Value}"; + else if (solidFill?.SchemeColor?.Val?.Value != null) + textElem.FontColor = $"#{solidFill.SchemeColor.Val}"; + } + + slideModel.Elements.Add(textElem); + } + + private static string GetExtension(string contentType) + { + return contentType switch + { + "image/png" => ".png", + "image/jpeg" => ".jpg", + "image/gif" => ".gif", + "image/bmp" => ".bmp", + "image/tiff" => ".tiff", + _ => ".bin" + }; + } +} diff --git a/Manager/MainWindow.xaml b/Manager/MainWindow.xaml index 7a7787f..ca866e9 100644 --- a/Manager/MainWindow.xaml +++ b/Manager/MainWindow.xaml @@ -57,7 +57,12 @@ - + + +