diff --git a/src/DocumentFormat.OpenXml.Flatten/DocumentFormat.OpenXml.Flatten/Compatibilities/Packaging/CompatiblePackage.cs b/src/DocumentFormat.OpenXml.Flatten/DocumentFormat.OpenXml.Flatten/Compatibilities/Packaging/CompatiblePackage.cs index 1c5176d..1cee884 100644 --- a/src/DocumentFormat.OpenXml.Flatten/DocumentFormat.OpenXml.Flatten/Compatibilities/Packaging/CompatiblePackage.cs +++ b/src/DocumentFormat.OpenXml.Flatten/DocumentFormat.OpenXml.Flatten/Compatibilities/Packaging/CompatiblePackage.cs @@ -697,8 +697,10 @@ internal void AddContentType(CompatiblePackage.PackUriHelper.ValidatedPartUri pa //partUri provided. Override takes precedence over the default entries if (_overrideDictionary != null) { - if (_overrideDictionary.ContainsKey(partUri)) - return _overrideDictionary[partUri]; + if (_overrideDictionary.TryGetValue(partUri, out var result)) + { + return result; + } } //Step 2: Check if there is a default entry corresponding to the @@ -789,9 +791,11 @@ private void EnsureOverrideDictionary() { // The part Uris are stored in the Override Dictionary in their original form , but they are compared // in a normalized manner using the PartUriComparer - if (_overrideDictionary == null) - _overrideDictionary = - new Dictionary(OverrideDictionaryInitialSize); + _overrideDictionary ??= new Dictionary( + OverrideDictionaryInitialSize, + // 这里需要忽略字符串的大小写 + // 修复 https://github.com/dotnet/Open-XML-SDK/issues/1355 + new ValidatedPartUriIgnoreCaseEqualityComparer()); } private void ParseContentTypesFile( @@ -1097,5 +1101,19 @@ private void ThrowIfXmlAttributeMissing(string attributeName, string? attributeV private const string PartNameAttributeName = "PartName"; private const string TemporaryPartNameWithoutExtension = "/tempfiles/sample."; } + + class ValidatedPartUriIgnoreCaseEqualityComparer : IEqualityComparer< + CompatiblePackage.PackUriHelper.ValidatedPartUri> + { + public bool Equals(PackUriHelper.ValidatedPartUri x, PackUriHelper.ValidatedPartUri y) + { + return StringComparer.OrdinalIgnoreCase.Equals(x.NormalizedPartUriString, y.NormalizedPartUriString); + } + + public int GetHashCode(PackUriHelper.ValidatedPartUri obj) + { + return StringComparer.OrdinalIgnoreCase.GetHashCode(obj.NormalizedPartUriString); + } + } } } diff --git a/src/DocumentFormat.OpenXml.Flatten/DocumentFormat.OpenXml.Flatten/Compatibilities/Packaging/EmptyPackagePart.cs b/src/DocumentFormat.OpenXml.Flatten/DocumentFormat.OpenXml.Flatten/Compatibilities/Packaging/EmptyPackagePart.cs index 0f24b11..9feb694 100644 --- a/src/DocumentFormat.OpenXml.Flatten/DocumentFormat.OpenXml.Flatten/Compatibilities/Packaging/EmptyPackagePart.cs +++ b/src/DocumentFormat.OpenXml.Flatten/DocumentFormat.OpenXml.Flatten/Compatibilities/Packaging/EmptyPackagePart.cs @@ -53,7 +53,7 @@ protected override string GetContentTypeCore() //Step 2: 通过路径进行特殊判断 // 例如 Slide 几的页面路径 var uri = partUri.OriginalString; - if (Regex.IsMatch(uri, @"/ppt/slides/slide\d+.xml")) + if (Regex.IsMatch(uri, @"/ppt/slides/slide\d+\.xml")) { // "/ppt/slides/slide0.xml" return new CompatiblePackage.ContentType( @@ -84,6 +84,20 @@ protected override string GetContentTypeCore() return new CompatiblePackage.ContentType("application/vnd.openxmlformats-officedocument.presentationml.notesMaster+xml"); } + if (Regex.IsMatch(uri, @"/ppt/diagrams/drawing\d+\.xml")) + { + // /ppt/diagrams/drawing0.xml + // application/vnd.ms-office.drawingml.diagramDrawing+xml + return new CompatiblePackage.ContentType("application/vnd.ms-office.drawingml.diagramDrawing+xml"); + } + + if (Regex.IsMatch(uri, @"/tags/tag\d+\.xml")) + { + // /tags/tag0.xml + // application/vnd.openxmlformats-officedocument.presentationml.tags+xml + return new CompatiblePackage.ContentType("application/vnd.openxmlformats-officedocument.presentationml.tags+xml"); + } + //Step 3: Check if there is a default entry corresponding to the //extension of the partUri provided. string extension = partUri.PartUriExtension; diff --git a/src/DocumentFormat.OpenXml.Flatten/DocumentFormat.OpenXml.Flatten/Contexts/GeometryPath.cs b/src/DocumentFormat.OpenXml.Flatten/DocumentFormat.OpenXml.Flatten/Contexts/GeometryPath.cs index e2e6f4e..46c18ce 100644 --- a/src/DocumentFormat.OpenXml.Flatten/DocumentFormat.OpenXml.Flatten/Contexts/GeometryPath.cs +++ b/src/DocumentFormat.OpenXml.Flatten/DocumentFormat.OpenXml.Flatten/Contexts/GeometryPath.cs @@ -13,18 +13,19 @@ public readonly struct ShapePath /// 创建PPT的Geometry Path /// /// OpenXml Path字符串 - /// OpenXml的Path Fill Mode - /// 是否有轮廓 - /// 指定使用 3D 拉伸可能在此路径 + /// OpenXml的Path Fill Mode:默认为Norm + /// 是否有轮廓:默认为True + /// 指定使用 3D 拉伸可能在此路径:默认为False /// 指定的宽度或在路径坐标系统中应在使用的最大的 x 坐标 /// 指定框架的高度或在路径坐标系统中应在使用的最大的 y 坐标 - public ShapePath(string path, PathFillModeValues fillMode = PathFillModeValues.Norm, bool isStroke = true, bool isExtrusionOk = false, double? emuWidth = null, double? emuHeight = null) + /// 参考文档:Ecma Office Open XML Part 1 - Fundamentals And Markup Language Reference - 20.1.9.15 path (Shape Path) + public ShapePath(string path, PathFillModeValues? fillMode = PathFillModeValues.Norm, bool? isStroke = true, bool? isExtrusionOk = false, double? emuWidth = null, double? emuHeight = null) { Path = path; - IsStroke = isStroke; - FillMode = fillMode; + IsStroke = isStroke ?? true; + FillMode = fillMode ?? PathFillModeValues.Norm; IsFilled = fillMode is not PathFillModeValues.None; - IsExtrusionOk = isExtrusionOk; + IsExtrusionOk = isExtrusionOk ?? false; Width = emuWidth.HasValue ? new Emu(emuWidth.Value) : null; Height = emuHeight.HasValue ? new Emu(emuHeight.Value) : null; } diff --git a/src/DocumentFormat.OpenXml.Flatten/DocumentFormat.OpenXml.Flatten/ElementConverters/CommonElement/CommonElementTransformDataExtensions.cs b/src/DocumentFormat.OpenXml.Flatten/DocumentFormat.OpenXml.Flatten/ElementConverters/CommonElement/CommonElementTransformDataExtensions.cs index b40d6f2..ccb1b09 100644 --- a/src/DocumentFormat.OpenXml.Flatten/DocumentFormat.OpenXml.Flatten/ElementConverters/CommonElement/CommonElementTransformDataExtensions.cs +++ b/src/DocumentFormat.OpenXml.Flatten/DocumentFormat.OpenXml.Flatten/ElementConverters/CommonElement/CommonElementTransformDataExtensions.cs @@ -119,6 +119,17 @@ internal static ITransformData CreateTransformData(this OpenXmlElement element, return transformData; } + var alternateContentTransform2D = element.GetFirstChild()?.GetFirstChild(); + if (alternateContentTransform2D is not null) + { + FillOffset(alternateContentTransform2D.Offset, transformData); + FillExtents(alternateContentTransform2D.Extents, transformData); + FillRotation(alternateContentTransform2D.Rotation, transformData); + FillFlip(alternateContentTransform2D.HorizontalFlip, alternateContentTransform2D.VerticalFlip, transformData); + return transformData; + } + + return transformData; void FillOffset(Offset? offset, TransformData transformData2) diff --git a/src/DocumentFormat.OpenXml.Flatten/DocumentFormat.OpenXml.Flatten/ElementConverters/Primitive/Colors_/ColorHelper.cs b/src/DocumentFormat.OpenXml.Flatten/DocumentFormat.OpenXml.Flatten/ElementConverters/Primitive/Colors_/ColorHelper.cs index f1318c8..7071d49 100644 --- a/src/DocumentFormat.OpenXml.Flatten/DocumentFormat.OpenXml.Flatten/ElementConverters/Primitive/Colors_/ColorHelper.cs +++ b/src/DocumentFormat.OpenXml.Flatten/DocumentFormat.OpenXml.Flatten/ElementConverters/Primitive/Colors_/ColorHelper.cs @@ -1,4 +1,5 @@ -using System.Globalization; +using System.Diagnostics; +using System.Globalization; using DocumentFormat.OpenXml.Drawing; using DocumentFormat.OpenXml.Flatten.Framework.Context; @@ -295,14 +296,37 @@ public static class ColorHelper /// public static Color? ToColor(this RgbColorModelHex color) { - if (color.Val is not null) + if (color.Val is null) { - if (uint.TryParse(color.Val.Value, NumberStyles.HexNumber, null, out var result)) - { - var solidColor = result.HexToColor(); - var modifiedColor = ColorTransform.AppendColorModify(solidColor, color.ChildElements); - return modifiedColor; - } + return null; + } + + var solidColor = ToColor(color.Val.Value); + if (solidColor is null) + { + return null; + } + var modifiedColor = ColorTransform.AppendColorModify(solidColor, color.ChildElements); + return modifiedColor; + + } + + /// + /// 将颜色值转换为 + /// + /// 颜色值:例如#E71224 + /// + public static Color? ToColor(this string? colorValue) + { + if (string.IsNullOrEmpty(colorValue)) + { + return null; + } + + var (success, a, r, g, b) = ConvertToColor(colorValue!); + if (success) + { + return new(a, r, g, b); } return null; @@ -358,6 +382,121 @@ private static Color HexToColor(this uint rgb) return color; } + /// + /// 将传入的颜色字符串转换为颜色输出 + /// + /// 颜色字符串,格式如 “#FFDFD991” 或 “#DFD991”等,规则和 WPF 的 XAML 颜色相同,其中 “#” 是可选的 + /// + public static (bool success, byte a, byte r, byte g, byte b) ConvertToColor(string hexColorText) + { +#if NET6_0_OR_GREATER + bool startWithPoundSign = hexColorText.StartsWith('#'); +#else + bool startWithPoundSign = hexColorText.StartsWith("#"); +#endif + var colorStringLength = hexColorText.Length; + if (startWithPoundSign) colorStringLength -= 1; + int currentOffset = startWithPoundSign ? 1 : 0; + // 可以采用的格式如下 + // #FFDFD991 8 个字符 存在 Alpha 通道 + // #DFD991 6 个字符 + // #FD92 4 个字符 存在 Alpha 通道 + // #DAC 3 个字符 + if (colorStringLength == 8 + || colorStringLength == 6 + || colorStringLength == 4 + || colorStringLength == 3) + { + bool success; + byte result; + byte a; + + int readCount; + // #DFD991 6 个字符 + // #FFDFD991 8 个字符 存在 Alpha 通道 + //if (colorStringLength == 8 || colorStringLength == 6) + if (colorStringLength > 5) + { + readCount = 2; + } + else + { + readCount = 1; + } + + bool includeAlphaChannel = colorStringLength == 8 || colorStringLength == 4; + + if (includeAlphaChannel) + { + (success, result) = HexCharToNumber(hexColorText, currentOffset, readCount); + if (!success) return default; + a = result; + currentOffset += readCount; + } + else + { + a = 0xFF; + } + + (success, result) = HexCharToNumber(hexColorText, currentOffset, readCount); + if (!success) return default; + byte r = result; + currentOffset += readCount; + + (success, result) = HexCharToNumber(hexColorText, currentOffset, readCount); + if (!success) return default; + byte g = result; + currentOffset += readCount; + + (success, result) = HexCharToNumber(hexColorText, currentOffset, readCount); + if (!success) return default; + byte b = result; + + return (true, a, r, g, b); + } + + return default; + } + + static (bool success, byte result) HexCharToNumber(string input, int offset, int readCount) + { + Debug.Assert(readCount == 1 || readCount == 2, "要求 readCount 只能是 1 或者 2 的值,这是框架限制,因此不做判断"); + + byte result = 0; + + for (int i = 0; i < readCount; i++, offset++) + { + var c = input[offset]; + byte n; + if (c >= '0' && c <= '9') + { + n = (byte) (c - '0'); + } + else if (c >= 'a' && c <= 'f') + { + n = (byte) (c - 'a' + 10); + } + else if (c >= 'A' && c <= 'F') + { + n = (byte) (c - 'A' + 10); + } + else + { + return default; + } + + result *= 16; + result += n; + } + + if (readCount == 1) + { + result = (byte) (result * 16 + result); + } + + return (true, result); + } + //private static ColorBrush? ToColorBrush([CanBeNull] this Color? color) //{ // if (color == null) return null; diff --git a/src/DocumentFormat.OpenXml.Flatten/DocumentFormat.OpenXml.Flatten/ElementConverters/Shape/CustomGeometryConverters/CustomGeometryConverter.cs b/src/DocumentFormat.OpenXml.Flatten/DocumentFormat.OpenXml.Flatten/ElementConverters/Shape/CustomGeometryConverters/CustomGeometryConverter.cs index 15dbec5..cb03281 100644 --- a/src/DocumentFormat.OpenXml.Flatten/DocumentFormat.OpenXml.Flatten/ElementConverters/Shape/CustomGeometryConverters/CustomGeometryConverter.cs +++ b/src/DocumentFormat.OpenXml.Flatten/DocumentFormat.OpenXml.Flatten/ElementConverters/Shape/CustomGeometryConverters/CustomGeometryConverter.cs @@ -38,6 +38,7 @@ public CustomGeometryConverter(CustomGeometry customGeometry, ElementEmuSize emu /// public SvgPath? Convert() { + ConvertAdjustValueList(); ConvertShapeGuideList(); ConvertShapeTextRectangle(); return ConvertPathList(); @@ -67,6 +68,25 @@ private void ConvertShapeTextRectangle() ShapeTextRectangle = new EmuShapeTextRectangle(left, top, right, bottom); } + private void ConvertAdjustValueList() + { + var adjustValueList = _customGeometry.AdjustValueList; + if (adjustValueList is not null) + { + foreach (var shapeGuide in adjustValueList.Elements().OfType()) + { + var name = shapeGuide.Name?.Value; + var formula = shapeGuide.Formula?.Value; + + if (!string.IsNullOrEmpty(name) && !string.IsNullOrEmpty(formula)) + { + ShapeGeometryFormulaCalculator.Calculate(name!, formula!); + } + } + } + } + + private void ConvertShapeGuideList() { var shapeGuideList = _customGeometry.ShapeGuideList; @@ -83,6 +103,7 @@ private void ConvertShapeGuideList() } } } + } private SvgPath? ConvertPathList() @@ -134,7 +155,13 @@ void TryClosePath() } } - svgPathList.Add(new ShapePath(stringPath.ToString())); + var pathFillModeValues = path.Fill?.Value; + var isStroke = path.Stroke?.Value; + var width = path.Width?.Value; + var height = path.Height?.Value; + var isExtrusionOk = path.ExtrusionOk?.Value; + + svgPathList.Add(new ShapePath(stringPath.ToString(), pathFillModeValues, isStroke, isExtrusionOk, width, height)); stringPath.Clear(); } diff --git a/src/DocumentFormat.OpenXml.Flatten/DocumentFormat.OpenXml.Flatten/ElementConverters/Shape/ShapeGeometryConverters/ShapeGeometryConverterHelper.cs b/src/DocumentFormat.OpenXml.Flatten/DocumentFormat.OpenXml.Flatten/ElementConverters/Shape/ShapeGeometryConverters/ShapeGeometryConverterHelper.cs index d989533..af33dda 100644 --- a/src/DocumentFormat.OpenXml.Flatten/DocumentFormat.OpenXml.Flatten/ElementConverters/Shape/ShapeGeometryConverters/ShapeGeometryConverterHelper.cs +++ b/src/DocumentFormat.OpenXml.Flatten/DocumentFormat.OpenXml.Flatten/ElementConverters/Shape/ShapeGeometryConverters/ShapeGeometryConverterHelper.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using System.Reflection; +using System.Text; using System.Xml.Linq; using DocumentFormat.OpenXml.Drawing; @@ -268,11 +269,27 @@ public static class ShapeGeometryConverterHelper return default; } - var path = shapeGeometryBase.ToGeometryPathString(emuSize, adjustList); var multiGeometryPaths = shapeGeometryBase.GetMultiShapePaths(emuSize, adjustList); + + var pathString = shapeGeometryBase.ToGeometryPathString(emuSize, adjustList); + var path = string.IsNullOrWhiteSpace(pathString) ? GetSvgPath(multiGeometryPaths) : pathString; return new SvgPath(geometryShapeTypeValues, path, shapeGeometryBase.ShapeTextRectangle, multiGeometryPaths); } + private static string? GetSvgPath(ShapePath[]? shapePaths) + { + if (shapePaths is not null) + { + var sb = new StringBuilder(); + foreach (var shapePath in shapePaths) + { + sb.Append(shapePath.Path); + } + return sb.ToString(); + } + return default; + } + /// /// 根据调整点Name获取调整点的Value /// diff --git a/src/DocumentFormat.OpenXml.Flatten/DocumentFormat.OpenXml.Flatten/ElementConverters/Table/TableStyleEntries/NoStyleNoGrid_2D5ABB26_0587_4C30_8999_92F81FD0307C.cs b/src/DocumentFormat.OpenXml.Flatten/DocumentFormat.OpenXml.Flatten/ElementConverters/Table/TableStyleEntries/NoStyleNoGrid_2D5ABB26_0587_4C30_8999_92F81FD0307C.cs new file mode 100644 index 0000000..5e8857c --- /dev/null +++ b/src/DocumentFormat.OpenXml.Flatten/DocumentFormat.OpenXml.Flatten/ElementConverters/Table/TableStyleEntries/NoStyleNoGrid_2D5ABB26_0587_4C30_8999_92F81FD0307C.cs @@ -0,0 +1,106 @@ +using System.CodeDom.Compiler; + +using DocumentFormat.OpenXml.Drawing; + +namespace DocumentFormat.OpenXml.Flatten.ElementConverters.TableStyleEntries; + +[GeneratedCode("OpenXmlSdkTool", "2.5")] +public static class NoStyleNoGrid_2D5ABB26_0587_4C30_8999_92F81FD0307C +{ + public static TableStyleEntry GenerateTableStyleEntry() + { + TableStyleEntry tableStyleEntry1 = new TableStyleEntry() { StyleId = "{2D5ABB26-0587-4C30-8999-92F81FD0307C}", StyleName = "无样式,无网格" }; + + WholeTable wholeTable1 = new WholeTable(); + + TableCellTextStyle tableCellTextStyle1 = new TableCellTextStyle(); + + FontReference fontReference1 = new FontReference() { Index = FontCollectionIndexValues.Minor }; + RgbColorModelPercentage rgbColorModelPercentage1 = new RgbColorModelPercentage() { RedPortion = 0, GreenPortion = 0, BluePortion = 0 }; + + fontReference1.Append(rgbColorModelPercentage1); + SchemeColor schemeColor1 = new SchemeColor() { Val = SchemeColorValues.Text1 }; + + tableCellTextStyle1.Append(fontReference1); + tableCellTextStyle1.Append(schemeColor1); + + TableCellStyle tableCellStyle1 = new TableCellStyle(); + + TableCellBorders tableCellBorders1 = new TableCellBorders(); + + LeftBorder leftBorder1 = new LeftBorder(); + + Outline outline1 = new Outline(); + NoFill noFill1 = new NoFill(); + + outline1.Append(noFill1); + + leftBorder1.Append(outline1); + + RightBorder rightBorder1 = new RightBorder(); + + Outline outline2 = new Outline(); + NoFill noFill2 = new NoFill(); + + outline2.Append(noFill2); + + rightBorder1.Append(outline2); + + TopBorder topBorder1 = new TopBorder(); + + Outline outline3 = new Outline(); + NoFill noFill3 = new NoFill(); + + outline3.Append(noFill3); + + topBorder1.Append(outline3); + + BottomBorder bottomBorder1 = new BottomBorder(); + + Outline outline4 = new Outline(); + NoFill noFill4 = new NoFill(); + + outline4.Append(noFill4); + + bottomBorder1.Append(outline4); + + InsideHorizontalBorder insideHorizontalBorder1 = new InsideHorizontalBorder(); + + Outline outline5 = new Outline(); + NoFill noFill5 = new NoFill(); + + outline5.Append(noFill5); + + insideHorizontalBorder1.Append(outline5); + + InsideVerticalBorder insideVerticalBorder1 = new InsideVerticalBorder(); + + Outline outline6 = new Outline(); + NoFill noFill6 = new NoFill(); + + outline6.Append(noFill6); + + insideVerticalBorder1.Append(outline6); + + tableCellBorders1.Append(leftBorder1); + tableCellBorders1.Append(rightBorder1); + tableCellBorders1.Append(topBorder1); + tableCellBorders1.Append(bottomBorder1); + tableCellBorders1.Append(insideHorizontalBorder1); + tableCellBorders1.Append(insideVerticalBorder1); + + FillProperties fillProperties1 = new FillProperties(); + NoFill noFill7 = new NoFill(); + + fillProperties1.Append(noFill7); + + tableCellStyle1.Append(tableCellBorders1); + tableCellStyle1.Append(fillProperties1); + + wholeTable1.Append(tableCellTextStyle1); + wholeTable1.Append(tableCellStyle1); + + tableStyleEntry1.Append(wholeTable1); + return tableStyleEntry1; + } +} diff --git a/src/DocumentFormat.OpenXml.Flatten/DocumentFormat.OpenXml.Flatten/ElementConverters/Table/TableStyleEntries/TableStyleEntryProvider.cs b/src/DocumentFormat.OpenXml.Flatten/DocumentFormat.OpenXml.Flatten/ElementConverters/Table/TableStyleEntries/TableStyleEntryProvider.cs index e74f0c7..3afa81a 100644 --- a/src/DocumentFormat.OpenXml.Flatten/DocumentFormat.OpenXml.Flatten/ElementConverters/Table/TableStyleEntries/TableStyleEntryProvider.cs +++ b/src/DocumentFormat.OpenXml.Flatten/DocumentFormat.OpenXml.Flatten/ElementConverters/Table/TableStyleEntries/TableStyleEntryProvider.cs @@ -20,10 +20,14 @@ public static class TableStyleEntryProvider /// /// 此代码是生成代码,通过 OpenXmlSdkTool.exe 生成,工具版本 V2.5。 /// 生成代码方法请看 + /// 文档: MS-OE376 的 2.1.1343 Part 4 Section 5.1.6.10, tableStyleId (Table Style ID + /// 文档里面有各个样式的对应值 public static TableStyleEntry? CreateTableStyleEntry(string styleId) { return styleId switch { + "{2D5ABB26-0587-4C30-8999-92F81FD0307C}" => NoStyleNoGrid_2D5ABB26_0587_4C30_8999_92F81FD0307C.GenerateTableStyleEntry(), + "{284E427A-3D55-4303-BF80-6455036E1DE7}" => ThemedStyle1Accent2_284E427A_3D55_4303_BF80_6455036E1DE7.GenerateTableStyleEntry(), "{69C7853C-536D-4A76-A0AE-DD22124D55A5}" => ThemedStyle1Accent3_69C7853C_536D_4A76_A0AE_DD22124D55A5.GenerateTableStyleEntry(), "{775DCB02-9BB8-47FD-8907-85C794F793BA}" => ThemedStyle1Accent4_775DCB02_9BB8_47FD_8907_85C794F793BA.GenerateTableStyleEntry(),