619 lines
24 KiB
C#
619 lines
24 KiB
C#
|
|
using System;
|
||
|
|
using System.Collections.Generic;
|
||
|
|
using System.IO;
|
||
|
|
using System.IO.Packaging;
|
||
|
|
using System.Linq;
|
||
|
|
using System.Xml.Linq;
|
||
|
|
|
||
|
|
namespace BetterSeewo.Manager.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;
|
||
|
|
|
||
|
|
foreach (var elem in spTree.Elements())
|
||
|
|
{
|
||
|
|
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":
|
||
|
|
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 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 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 = pic.Element(P + "blipFill")?.Element(A + "blip");
|
||
|
|
var embed = blip?.Attribute(R + "embed")?.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");
|
||
|
|
if (target.StartsWith("../media/", StringComparison.OrdinalIgnoreCase))
|
||
|
|
target = target.Substring("../".Length);
|
||
|
|
|
||
|
|
var fileName = Path.GetFileName(target);
|
||
|
|
|
||
|
|
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") ?? graphicFrame.Element(P + "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.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) };
|
||
|
|
}
|
||
|
|
|
||
|
|
row.Cells.Add(cell);
|
||
|
|
}
|
||
|
|
table.Rows.Add(row);
|
||
|
|
}
|
||
|
|
|
||
|
|
slide.Elements.Add(table);
|
||
|
|
}
|
||
|
|
|
||
|
|
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";
|
||
|
|
|
||
|
|
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,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
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 || 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");
|
||
|
|
|
||
|
|
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)
|
||
|
|
return new SmfColor { Value = (string)srgbClr.Attribute("val") ?? "000000" };
|
||
|
|
var schemeClr = parent.Element(A + "schemeClr");
|
||
|
|
if (schemeClr != null)
|
||
|
|
return new SmfColor { Value = SchemeToRgb((string)schemeClr.Attribute("val") ?? "dk1") };
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static string SchemeToRgb(string scheme) => 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();
|
||
|
|
|
||
|
|
if (spPr.Element(A + "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;
|
||
|
|
}
|
||
|
|
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
|
||
|
|
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,
|
||
|
|
_ => SmfStrokeDashStyle.Solid,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
return stroke;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static SmfShapeType ParseShapeType(string prst) => 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,
|
||
|
|
"leftArrow" => SmfShapeType.ArrowLeft,
|
||
|
|
"upArrow" => SmfShapeType.ArrowUp,
|
||
|
|
"downArrow" => SmfShapeType.ArrowDown,
|
||
|
|
"line" or "straightConnector1" => SmfShapeType.Line,
|
||
|
|
"cloud" => SmfShapeType.Cloud,
|
||
|
|
"heart" => SmfShapeType.Heart,
|
||
|
|
"cross" => SmfShapeType.Cross,
|
||
|
|
"chevron" => SmfShapeType.Chevron,
|
||
|
|
"pie" => SmfShapeType.Pie,
|
||
|
|
"blockArc" => SmfShapeType.BlockArc,
|
||
|
|
_ => 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", ".mp3" => "audio/mpeg",
|
||
|
|
_ => "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<double, double> 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)
|
||
|
|
{
|
||
|
|
return pres.Root?.Descendants().FirstOrDefault(d => d.Name == name)?.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<string>();
|
||
|
|
if (sldIdLst != null)
|
||
|
|
foreach (var sldId in sldIdLst.Elements(P + "sldId"))
|
||
|
|
order.Add((string)sldId.Attribute(R + "id"));
|
||
|
|
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|