diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 9e4b197c..29aa78eb 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -1,6 +1,6 @@ name: .NET build and test env: - CURRENT_VERSION: 8.0.${{ github.run_number }} + CURRENT_VERSION: 9.0.${{ github.run_number }} LAST_COMMIT_MESSAGE: ${{ github.event.head_commit.message }} on: @@ -14,7 +14,7 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup .NET uses: actions/setup-dotnet@v4 with: @@ -37,7 +37,7 @@ jobs: runs-on: ubuntu-latest needs: build steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup .NET uses: actions/setup-dotnet@v4 with: @@ -65,7 +65,7 @@ jobs: runs-on: ubuntu-latest needs: build steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup .NET uses: actions/setup-dotnet@v4 with: diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..45341b2e --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,21 @@ +# Build and Test Instructions + +## Prerequisites +- .NET 8 SDK (`dotnet-sdk-8.0`) must be installed. + ```bash + apt-get update && apt-get install -y dotnet-sdk-8.0 + ``` + +## Build +From the repository root, run: +```bash +dotnet build +``` +This restores NuGet packages and compiles the library and example projects. + +## Test +Execute the unit tests with: +```bash +dotnet test +``` +The command builds required projects and runs the test suite. diff --git a/OpenXmlPowerTools.Tests/ColorParserTests.cs b/OpenXmlPowerTools.Tests/ColorParserTests.cs new file mode 100644 index 00000000..c4cf50bd --- /dev/null +++ b/OpenXmlPowerTools.Tests/ColorParserTests.cs @@ -0,0 +1,56 @@ +using Codeuctivity.OpenXmlPowerTools; +using SkiaSharp; +using Xunit; + +namespace Codeuctivity.Tests +{ + public class ColorParserTests + { + [Theory] + [InlineData("red", 255, 0, 0)] + [InlineData("RED", 255, 0, 0)] + [InlineData("#00FF00", 0, 255, 0)] + [InlineData("#00ff00", 0, 255, 0)] + [InlineData("blue", 0, 0, 255)] + [InlineData("#0000FF", 0, 0, 255)] + [InlineData("#abc", 170, 187, 204)] + [InlineData("yellow", 255, 255, 0)] + [InlineData("black", 0, 0, 0)] + [InlineData("white", 255, 255, 255)] + public void ShouldParseColors(string input, byte r, byte g, byte b) + { + var result = ColorParser.FromName(input); + Assert.Equal(r, result.Red); + Assert.Equal(g, result.Green); + Assert.Equal(b, result.Blue); + } + + [Theory] + [InlineData("red", true)] + [InlineData("#123456", true)] + [InlineData("notacolor", false)] + [InlineData("", false)] + public void ShouldValidateColorNames(string input, bool valid) + { + Assert.Equal(valid, ColorParser.IsValidName(input)); + } + + [Fact] + public void FromNameShouldThrowOnInvalid() + { + Assert.Throws(() => ColorParser.FromName("bogus")); + } + + [Theory] + [InlineData("notacolor")] + [InlineData("#GGGGGG")] + [InlineData("")] + [InlineData(null)] + [InlineData(" ")] + public void ShouldRejectInvalidColors(string? input) + { + var success = ColorParser.TryFromName(input, out SKColor _); + Assert.False(success); + } + } +} diff --git a/OpenXmlPowerTools.Tests/CssPropertyValueTests.cs b/OpenXmlPowerTools.Tests/CssPropertyValueTests.cs new file mode 100644 index 00000000..8aaa38e5 --- /dev/null +++ b/OpenXmlPowerTools.Tests/CssPropertyValueTests.cs @@ -0,0 +1,45 @@ +using Codeuctivity.OpenXmlPowerTools; +using SkiaSharp; +using Xunit; + +namespace Codeuctivity.Tests +{ + public class CssPropertyValueTests + { + [Fact] + public void ShouldRecognizeNamedColor() + { + var value = new CssPropertyValue { Type = CssValueType.String, Value = "red" }; + Assert.True(value.IsColor); + var color = value.ToColor(); + Assert.Equal(SKColors.Red, color); + } + + [Fact] + public void ShouldRecognizeHexColor() + { + var value = new CssPropertyValue { Type = CssValueType.Hex, Value = "#0000FF" }; + Assert.True(value.IsColor); + var color = value.ToColor(); + Assert.Equal(SKColors.Blue, color); + } + + [Fact] + public void ShouldRejectNonColor() + { + var value = new CssPropertyValue { Type = CssValueType.String, Value = "1234" }; + Assert.False(value.IsColor); + } + + [Fact] + public void ShouldParseHexWithoutHash() + { + var value = new CssPropertyValue { Type = CssValueType.Hex, Value = "00FF00" }; + Assert.True(value.IsColor); + var color = value.ToColor(); + Assert.Equal(0u, color.Red); + Assert.Equal(255u, color.Green); + Assert.Equal(0u, color.Blue); + } + } +} diff --git a/OpenXmlPowerTools.Tests/ImageHandlerTests.cs b/OpenXmlPowerTools.Tests/ImageHandlerTests.cs new file mode 100644 index 00000000..4e8b3d8d --- /dev/null +++ b/OpenXmlPowerTools.Tests/ImageHandlerTests.cs @@ -0,0 +1,93 @@ +using System.IO; +using System.Xml.Linq; +using Codeuctivity.OpenXmlPowerTools; +using Codeuctivity.OpenXmlPowerTools.OpenXMLWordprocessingMLToHtmlConverter; +using SkiaSharp; +using Xunit; + +namespace Codeuctivity.Tests +{ + public class ImageHandlerTests + { + [Theory] + [InlineData(SKEncodedImageFormat.Png, "image/png")] + [InlineData(SKEncodedImageFormat.Jpeg, "image/jpeg")] + [InlineData(SKEncodedImageFormat.Webp, "image/webp")] + public void ShouldTransformImagesToDataUri(SKEncodedImageFormat format, string mime) + { + using var surface = SKSurface.Create(new SKImageInfo(10, 10)); + surface.Canvas.Clear(SKColors.Red); + using var image = surface.Snapshot(); + using var ms = new MemoryStream(); + using (var data = image.Encode(format, 100)) + { + data.SaveTo(ms); + } + ms.Position = 0; + var info = new ImageInfo { Image = ms, AltText = "alt", ImgStyleAttribute = new XAttribute(NoNamespace.style, "width:10px") }; + var handler = new ImageHandler(); + var result = handler.TransformImage(info); + var srcAttr = result.Attribute(NoNamespace.src); + Assert.NotNull(srcAttr); + Assert.StartsWith($"data:{mime};base64,", srcAttr.Value); + Assert.Equal("width:10px", result.Attribute(NoNamespace.style)?.Value); + } + + [Fact] + public void ShouldTransformGifToDataUri() + { + var gif = System.Convert.FromBase64String("R0lGODlhAQABAPAAAP///wAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw=="); + using var ms = new MemoryStream(gif); + var info = new ImageInfo { Image = ms }; + var handler = new ImageHandler(); + var result = handler.TransformImage(info); + var srcAttr = result.Attribute(NoNamespace.src); + Assert.NotNull(srcAttr); + Assert.StartsWith("data:image/gif;base64,", srcAttr.Value); + } + + [Fact] + public void ShouldThrowOnInvalidImage() + { + using var ms = new MemoryStream(new byte[] { 1, 2, 3, 4 }); + var handler = new ImageHandler(); + Assert.ThrowsAny(() => handler.TransformImage(new ImageInfo { Image = ms })); + } + + [Fact] + public void ShouldIncludeAltText() + { + using var surface = SKSurface.Create(new SKImageInfo(5, 5)); + surface.Canvas.Clear(SKColors.Blue); + using var image = surface.Snapshot(); + using var ms = new MemoryStream(); + using (var data = image.Encode(SKEncodedImageFormat.Png, 100)) + { + data.SaveTo(ms); + } + ms.Position = 0; + var info = new ImageInfo { Image = ms, AltText = "demo" }; + var handler = new ImageHandler(); + var result = handler.TransformImage(info); + Assert.Equal("demo", result.Attribute(NoNamespace.alt)?.Value); + } + + [Fact] + public void ShouldOmitAltTextWhenNotProvided() + { + using var surface = SKSurface.Create(new SKImageInfo(5, 5)); + surface.Canvas.Clear(SKColors.Blue); + using var image = surface.Snapshot(); + using var ms = new MemoryStream(); + using (var data = image.Encode(SKEncodedImageFormat.Png, 100)) + { + data.SaveTo(ms); + } + ms.Position = 0; + var info = new ImageInfo { Image = ms }; + var handler = new ImageHandler(); + var result = handler.TransformImage(info); + Assert.Null(result.Attribute(NoNamespace.alt)); + } + } +} diff --git a/OpenXmlPowerTools.Tests/OpenXmlPowerTools.Tests.csproj b/OpenXmlPowerTools.Tests/OpenXmlPowerTools.Tests.csproj index 6ef5224e..9b8f7ac4 100644 --- a/OpenXmlPowerTools.Tests/OpenXmlPowerTools.Tests.csproj +++ b/OpenXmlPowerTools.Tests/OpenXmlPowerTools.Tests.csproj @@ -8,6 +8,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/OpenXmlPowerTools/ColorParser.cs b/OpenXmlPowerTools/ColorParser.cs index 38d3066b..993abb4f 100644 --- a/OpenXmlPowerTools/ColorParser.cs +++ b/OpenXmlPowerTools/ColorParser.cs @@ -1,17 +1,39 @@ -using SixLabors.ImageSharp; +using SkiaSharp; +using System; +using System.Drawing; namespace Codeuctivity.OpenXmlPowerTools { public static class ColorParser { - public static Color FromName(string name) + public static SKColor FromName(string name) { - return Color.Parse(name); + if (!TryFromName(name, out var color)) + { + throw new ArgumentException("Invalid color name", nameof(name)); + } + return color; } - public static bool TryFromName(string? name, out Color color) + public static bool TryFromName(string? name, out SKColor color) { - return Color.TryParse(name, out color); + if (string.IsNullOrWhiteSpace(name)) + { + color = default; + return false; + } + + try + { + var drawingColor = ColorTranslator.FromHtml(name); + color = new SKColor(drawingColor.R, drawingColor.G, drawingColor.B, drawingColor.A); + return true; + } + catch + { + color = default; + return false; + } } public static bool IsValidName(string name) @@ -19,4 +41,4 @@ public static bool IsValidName(string name) return TryFromName(name, out _); } } -} \ No newline at end of file +} diff --git a/OpenXmlPowerTools/HtmlToWmlConverterCore.cs b/OpenXmlPowerTools/HtmlToWmlConverterCore.cs index 24d97fc5..97fc3d0f 100644 --- a/OpenXmlPowerTools/HtmlToWmlConverterCore.cs +++ b/OpenXmlPowerTools/HtmlToWmlConverterCore.cs @@ -1,4 +1,4 @@ -/*************************************************************************** +/*************************************************************************** * HTML elements handled in this module: * * a @@ -95,8 +95,7 @@ using DocumentFormat.OpenXml.Packaging; using SixLabors.Fonts; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Formats; +using SkiaSharp; using System; using System.Collections.Generic; using System.IO; @@ -2423,24 +2422,35 @@ private static int GetNextRectId() private static XElement? TransformImageToWml(XElement element, HtmlToWmlConverterSettings settings, WordprocessingDocument wDoc) { var srcAttribute = (string)element.Attribute(XhtmlNoNamespace.src); - Image? bmp = null; - IImageFormat format; - + SKBitmap? bmp = null; byte[] ba; if (srcAttribute.StartsWith("data:")) { var semiIndex = srcAttribute.IndexOf(';'); var commaIndex = srcAttribute.IndexOf(',', semiIndex); var base64 = srcAttribute.Substring(commaIndex + 1); - ba = Convert.FromBase64String(base64); - using var ms = new MemoryStream(ba); - bmp = Image.Load(ms, out format); + var raw = Convert.FromBase64String(base64); + bmp = SKBitmap.Decode(raw); + if (bmp == null) + { + return null; + } + using var image = SKImage.FromBitmap(bmp); + using var data = image.Encode(SKEncodedImageFormat.Png, 100); + ba = data.ToArray(); } else { try { - bmp = Image.Load(Path.Combine(settings.BaseUriForImages, srcAttribute), out format); + bmp = SKBitmap.Decode(Path.Combine(settings.BaseUriForImages, srcAttribute)); + if (bmp == null) + { + return null; + } + using var image = SKImage.FromBitmap(bmp); + using var data = image.Encode(SKEncodedImageFormat.Png, 100); + ba = data.ToArray(); } catch (ArgumentException) { @@ -2450,9 +2460,6 @@ private static int GetNextRectId() { return null; } - var ms = new MemoryStream(); - bmp.Save(ms, format); - ba = ms.ToArray(); } var mdp = wDoc.MainDocumentPart; @@ -2461,7 +2468,7 @@ private static int GetNextRectId() var newPart = mdp.AddImagePart(ipt, rId); using (var s = newPart.GetStream(FileMode.Create, FileAccess.ReadWrite)) { - s.Write(ba, 0, ba.GetUpperBound(0) + 1); + s.Write(ba, 0, ba.Length); } var pid = wDoc.Annotation(); @@ -2495,7 +2502,7 @@ private static int GetNextRectId() return null; } - private static XElement GetImageAsInline(XElement element, Image bmp, string rId, int pictureId, string pictureDescription) + private static XElement GetImageAsInline(XElement element, SKBitmap bmp, string rId, int pictureId, string pictureDescription) { var inline = new XElement(WP.inline, // 20.4.2.8 new XAttribute(XNamespace.Xmlns + "wp", WP.wp.NamespaceName), @@ -2511,7 +2518,7 @@ private static XElement GetImageAsInline(XElement element, Image bmp, string rId return inline; } - private static XElement GetImageAsAnchor(XElement element, HtmlToWmlConverterSettings settings, Image bmp, string rId, string floatValue, int pictureId, string pictureDescription) + private static XElement GetImageAsAnchor(XElement element, HtmlToWmlConverterSettings settings, SKBitmap bmp, string rId, string floatValue, int pictureId, string pictureDescription) { Emu minDistFromEdge = (long)(0.125 * Emu.s_EmusPerInch); long relHeight = 251658240; // z-order @@ -2626,13 +2633,11 @@ private static XElement GetRunPropertiesForImage() new XElement(W.noProof)); } - private static SizeEmu GetImageSizeInEmus(XElement img, Image bmp) + private static SizeEmu GetImageSizeInEmus(XElement img, SKBitmap bmp) { - var hres = bmp.Metadata.HorizontalResolution; - var vres = bmp.Metadata.VerticalResolution; - var s = bmp.Size(); - Emu cx = (long)(s.Width / hres * Emu.s_EmusPerInch); - Emu cy = (long)(s.Height / vres * Emu.s_EmusPerInch); + const float dpi = 96f; + Emu cx = (long)(bmp.Width / dpi * Emu.s_EmusPerInch); + Emu cy = (long)(bmp.Height / dpi * Emu.s_EmusPerInch); var width = img.GetProp("width"); var height = img.GetProp("height"); @@ -2659,7 +2664,7 @@ private static SizeEmu GetImageSizeInEmus(XElement img, Image bmp) return new SizeEmu(cx, cy); } - private static XElement GetImageExtent(XElement img, Image bmp) + private static XElement GetImageExtent(XElement img, SKBitmap bmp) { var szEmu = GetImageSizeInEmus(img, bmp); return new XElement(WP.extent, @@ -2692,7 +2697,7 @@ private static XElement GetCNvGraphicFramePr() new XAttribute(NoNamespace.noChangeAspect, 1))); } - private static XElement GetGraphicForImage(XElement element, string rId, Image bmp, int pictureId, string pictureDescription) + private static XElement GetGraphicForImage(XElement element, string rId, SKBitmap bmp, int pictureId, string pictureDescription) { var szEmu = GetImageSizeInEmus(element, bmp); var graphic = new XElement(A.graphic, diff --git a/OpenXmlPowerTools/HtmlToWmlCssParser.cs b/OpenXmlPowerTools/HtmlToWmlCssParser.cs index 77d761b3..c9f36ecb 100644 --- a/OpenXmlPowerTools/HtmlToWmlCssParser.cs +++ b/OpenXmlPowerTools/HtmlToWmlCssParser.cs @@ -1,5 +1,4 @@ -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.PixelFormats; +using SkiaSharp; using System; using System.Collections; using System.Collections.Generic; @@ -592,7 +591,7 @@ public bool IsColor } } - public Color ToColor() + public SKColor ToColor() { var hex = "000000"; if (Type == CssValueType.Hex) @@ -616,7 +615,7 @@ public Color ToColor() var r = ConvertFromHex(hex.Substring(0, 2)); var g = ConvertFromHex(hex.Substring(2, 2)); var b = ConvertFromHex(hex.Substring(4)); - return Color.FromRgb(r, g, b); + return new SKColor(r, g, b); } private byte ConvertFromHex(string input) @@ -1081,7 +1080,7 @@ private int GetHueValue(CssTerm t) return 0; } - public Color ToColor() + public SKColor ToColor() { var hex = "000000"; if (Type == CssTermType.Hex) @@ -1106,7 +1105,7 @@ public Color ToColor() { if (Function.Expression.Terms[i].Type != CssTermType.Number) { - return Color.Black; + return SKColors.Black; } switch (i) { @@ -1123,7 +1122,7 @@ public Color ToColor() break; } } - return Color.FromRgb(fr, fg, fb); + return new SKColor(fr, fg, fb); } else if (Function.Name.ToLower().Equals("hsl") && Function.Expression.Terms.Count == 3 || Function.Name.Equals("hsla") && Function.Expression.Terms.Count == 4 @@ -1132,7 +1131,7 @@ public Color ToColor() int h = 0, s = 0, v = 0; for (var i = 0; i < Function.Expression.Terms.Count; i++) { - if (Function.Expression.Terms[i].Type != CssTermType.Number) { return Color.Black; } + if (Function.Expression.Terms[i].Type != CssTermType.Number) { return SKColors.Black; } switch (i) { case 0: @@ -1171,7 +1170,7 @@ public Color ToColor() var r = ConvertFromHex(hex.Substring(0, 2)); var g = ConvertFromHex(hex.Substring(2, 2)); var b = ConvertFromHex(hex.Substring(4)); - return Color.FromRgb(r, g, b); + return new SKColor(r, g, b); } private byte ConvertFromHex(string input) @@ -1317,7 +1316,7 @@ public HueSatVal(int h, int s, int v) Value = v; } - public HueSatVal(Color color) + public HueSatVal(SKColor color) { Hue = 0; Saturation = 0; @@ -1331,13 +1330,13 @@ public HueSatVal(Color color) public int Value { get; set; } - public Color Color + public SKColor Color { get => ConvertToRGB(); set => ConvertFromRGB(value); } - private void ConvertFromRGB(Color color) + private void ConvertFromRGB(SKColor color) { double min; double max; double delta; var r = CalcRedConponent(color); @@ -1380,22 +1379,22 @@ private void ConvertFromRGB(Color color) Value = (int)(v * 255.0d); } - private double CalcRedConponent(Color color) + private double CalcRedConponent(SKColor color) { - return color.ToPixel().R; + return color.Red; } - private double CalcGreenConponent(Color color) + private double CalcGreenConponent(SKColor color) { - return color.ToPixel().G; + return color.Green; } - private double CalcBlueConponent(Color color) + private double CalcBlueConponent(SKColor color) { - return color.ToPixel().B; + return color.Blue; } - private Color ConvertToRGB() + private SKColor ConvertToRGB() { double h; double s; @@ -1472,7 +1471,7 @@ private Color ConvertToRGB() break; } } - return Color.FromRgb((byte)(r * 255.0d), (byte)(g * 255.0d), (byte)(b * 255.0d)); + return new SKColor((byte)(r * 255.0d), (byte)(g * 255.0d), (byte)(b * 255.0d)); } public static bool operator !=(HueSatVal left, HueSatVal right) diff --git a/OpenXmlPowerTools/OpenXMLWordprocessingMLToHtmlConverter/ImageHandler.cs b/OpenXmlPowerTools/OpenXMLWordprocessingMLToHtmlConverter/ImageHandler.cs index 7df6c3e0..f42c9a93 100644 --- a/OpenXmlPowerTools/OpenXMLWordprocessingMLToHtmlConverter/ImageHandler.cs +++ b/OpenXmlPowerTools/OpenXMLWordprocessingMLToHtmlConverter/ImageHandler.cs @@ -1,5 +1,4 @@ -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Formats; +using SkiaSharp; using System; using System.IO; using System.Xml.Linq; @@ -18,17 +17,28 @@ public class ImageHandler : IImageHandler /// public XElement TransformImage(ImageInfo imageInfo) { - IImageFormat format; using var imageStream = new MemoryStream(); imageInfo.Image.CopyTo(imageStream); - imageStream.Position = 0; - using var image = Image.Load(imageStream, out format); - var base64 = Convert.ToBase64String(imageStream.ToArray()); - var mimeType = format.DefaultMimeType; + var data = imageStream.ToArray(); + using var codec = SKCodec.Create(new SKMemoryStream(data)); + var mimeType = GetMimeType(codec.EncodedFormat); + var base64 = Convert.ToBase64String(data); var imageSource = $"data:{mimeType};base64,{base64}"; return new XElement(Xhtml.img, new XAttribute(NoNamespace.src, imageSource), imageInfo.ImgStyleAttribute, imageInfo.AltText != null ? new XAttribute(NoNamespace.alt, imageInfo.AltText) : null); } + + private static string GetMimeType(SKEncodedImageFormat format) => format switch + { + SKEncodedImageFormat.Bmp => "image/bmp", + SKEncodedImageFormat.Gif => "image/gif", + SKEncodedImageFormat.Ico => "image/x-icon", + SKEncodedImageFormat.Jpeg => "image/jpeg", + SKEncodedImageFormat.Png => "image/png", + SKEncodedImageFormat.Wbmp => "image/vnd.wap.wbmp", + SKEncodedImageFormat.Webp => "image/webp", + _ => "application/octet-stream", + }; } -} \ No newline at end of file +} diff --git a/OpenXmlPowerTools/OpenXmlPowerTools.csproj b/OpenXmlPowerTools/OpenXmlPowerTools.csproj index 36d067ff..208cf72a 100644 --- a/OpenXmlPowerTools/OpenXmlPowerTools.csproj +++ b/OpenXmlPowerTools/OpenXmlPowerTools.csproj @@ -1,60 +1,61 @@ - - - net8.0 - true - true - https://github.com/Codeuctivity/OpenXmlPowerTools - OpenXML DOCX Word XLSX Excel PPTX Powerpoint - Stefan Seeland - Codeuctivity - $(CURRENT_VERSION) - 0.0.1 - $(Version) - $(Version) - $(Version) - $(LAST_COMMIT_MESSAGE) - NugetIcon.png - https://github.com/Codeuctivity/OpenXmlPowerTools - The Open XML SDK provides tools for working with Office Word, Excel, and PowerPoint documents. This fork supports current .net versions. - MIT - OpenXmlPowerTools.snk - true - true - snupkg - true - true - enable - 8.0 - Codeuctivity.OpenXmlPowerTools - en - true - Codeuctivity.OpenXmlPowerTools - Codeuctivity.OpenXmlPowerTools - nugetReadme.md - true - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - + + + net8.0 + true + true + https://github.com/Codeuctivity/OpenXmlPowerTools + OpenXML DOCX Word XLSX Excel PPTX Powerpoint + Stefan Seeland + Codeuctivity + $(CURRENT_VERSION) + 0.0.1 + $(Version) + $(Version) + $(Version) + $(LAST_COMMIT_MESSAGE) + NugetIcon.png + https://github.com/Codeuctivity/OpenXmlPowerTools + The Open XML SDK provides tools for working with Office Word, Excel, and PowerPoint documents. This fork supports current .net versions. + MIT + OpenXmlPowerTools.snk + true + true + snupkg + true + true + enable + 8.0 + Codeuctivity.OpenXmlPowerTools + en + true + Codeuctivity.OpenXmlPowerTools + Codeuctivity.OpenXmlPowerTools + nugetReadme.md + true + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + \ No newline at end of file diff --git a/OpenXmlPowerTools/OxPtHelpers.cs b/OpenXmlPowerTools/OxPtHelpers.cs index bc259560..2b027f55 100644 --- a/OpenXmlPowerTools/OxPtHelpers.cs +++ b/OpenXmlPowerTools/OxPtHelpers.cs @@ -50,14 +50,14 @@ public static WmlDocument AppendParagraphToDocument( if (!string.IsNullOrEmpty(foreColor)) { - SixLabors.ImageSharp.Color colorValue; + SkiaSharp.SKColor colorValue; if (!ColorParser.TryFromName(backColor, out colorValue)) { throw new OpenXmlPowerToolsException(string.Format("Add-DocxText: The specified color {0} is unsupported, Please specify the valid color. Ex, Red, Green", foreColor)); } - var ColorHex = string.Format("{0:x6}", colorValue); - runProperties.AppendChild(new Color() { Val = ColorHex.Substring(2) }); + var colorHex = $"{colorValue.Red:X2}{colorValue.Green:X2}{colorValue.Blue:X2}"; + runProperties.AppendChild(new Color() { Val = colorHex }); } if (isUnderline) @@ -67,14 +67,14 @@ public static WmlDocument AppendParagraphToDocument( if (!string.IsNullOrEmpty(backColor)) { - SixLabors.ImageSharp.Color colorShade; + SkiaSharp.SKColor colorShade; if (!ColorParser.TryFromName(backColor, out colorShade)) { throw new OpenXmlPowerToolsException(string.Format("Add-DocxText: The specified color {0} is unsupported, Please specify the valid color. Ex, Red, Green", foreColor)); } - var ColorShadeHex = string.Format("{0:x6}", colorShade); - runProperties.AppendChild(new Shading() { Fill = ColorShadeHex.Substring(2), Val = ShadingPatternValues.Clear }); + var colorShadeHex = $"{colorShade.Red:X2}{colorShade.Green:X2}{colorShade.Blue:X2}"; + runProperties.AppendChild(new Shading() { Fill = colorShadeHex, Val = ShadingPatternValues.Clear }); } if (!string.IsNullOrEmpty(styleName)) diff --git a/OpenXmlPowerToolsExamples/MarkupSimplifierApp/MarkupSimplifierApp.csproj b/OpenXmlPowerToolsExamples/MarkupSimplifierApp/MarkupSimplifierApp.csproj index 0073642f..07395205 100644 --- a/OpenXmlPowerToolsExamples/MarkupSimplifierApp/MarkupSimplifierApp.csproj +++ b/OpenXmlPowerToolsExamples/MarkupSimplifierApp/MarkupSimplifierApp.csproj @@ -9,7 +9,7 @@ - + diff --git a/README.md b/README.md index 56c7f42e..d12935be 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,12 @@ File.WriteAllText("./target.html", htmlString, Encoding.UTF8); enables writing XLSX files with millions of rows. - Extracting data (along with formatting) from spreadsheets. +## SkiaSharp migration + +Earlier releases used the ImageSharp library for tasks such as decoding images in the WordprocessingML to HTML converter and validating rendering output in tests. ImageSharp's powerful API came with a more restrictive licensing model that could require commercial agreements, which proved limiting for downstream projects. + +The project now uses SkiaSharp to handle these responsibilities. SkiaSharp, distributed under the permissive MIT license, provides cross-platform bindings to the Skia graphics engine. By leveraging SkiaSharp's `SKCodec` and `SKImage` APIs for image transformation and `SKColor` for color parsing, the codebase avoids licensing friction while retaining rich imaging capabilities. + ## Development - Run `dotnet build OpenXmlPowerTools.sln`