From 4f6337de6be50a802c1c258a36c59096dc37b6e6 Mon Sep 17 00:00:00 2001 From: "Janus C. H. Knudsen" Date: Thu, 5 Feb 2026 16:24:54 +0100 Subject: [PATCH] Adds basic POC for PDFToImage --- POC/PdfToImage.sln | 24 ++++ POC/PdfToImage/PdfToImage.csproj | 14 +++ POC/PdfToImage/Program.cs | 185 +++++++++++++++++++++++++++++++ 3 files changed, 223 insertions(+) create mode 100644 POC/PdfToImage.sln create mode 100644 POC/PdfToImage/PdfToImage.csproj create mode 100644 POC/PdfToImage/Program.cs diff --git a/POC/PdfToImage.sln b/POC/PdfToImage.sln new file mode 100644 index 0000000..7270139 --- /dev/null +++ b/POC/PdfToImage.sln @@ -0,0 +1,24 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.2.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PdfToImage", "PdfToImage\PdfToImage.csproj", "{515835BA-A8E0-C3F6-57E3-24410428E129}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {515835BA-A8E0-C3F6-57E3-24410428E129}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {515835BA-A8E0-C3F6-57E3-24410428E129}.Debug|Any CPU.Build.0 = Debug|Any CPU + {515835BA-A8E0-C3F6-57E3-24410428E129}.Release|Any CPU.ActiveCfg = Release|Any CPU + {515835BA-A8E0-C3F6-57E3-24410428E129}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {2F6D3A0F-356F-483E-863D-C672AEE8323E} + EndGlobalSection +EndGlobal diff --git a/POC/PdfToImage/PdfToImage.csproj b/POC/PdfToImage/PdfToImage.csproj new file mode 100644 index 0000000..71f6549 --- /dev/null +++ b/POC/PdfToImage/PdfToImage.csproj @@ -0,0 +1,14 @@ + + + + Exe + net9.0 + enable + enable + + + + + + + diff --git a/POC/PdfToImage/Program.cs b/POC/PdfToImage/Program.cs new file mode 100644 index 0000000..464ba76 --- /dev/null +++ b/POC/PdfToImage/Program.cs @@ -0,0 +1,185 @@ +using Docnet.Core; +using Docnet.Core.Models; + +Console.WriteLine("=== PDF til Billede Konverter ==="); +Console.WriteLine(); + +Console.Write("Indtast sti til PDF-fil: "); +string? pdfPath = Console.ReadLine()?.Trim().Trim('"'); + +if (string.IsNullOrEmpty(pdfPath)) +{ + Console.WriteLine("Fejl: Ingen fil angivet."); + Console.ReadKey(); + return; +} + +if (!File.Exists(pdfPath)) +{ + Console.WriteLine($"Fejl: Filen '{pdfPath}' blev ikke fundet."); + Console.ReadKey(); + return; +} + +Console.Write("Indtast output-mappe (tryk Enter for samme mappe som PDF): "); +string? outputInput = Console.ReadLine()?.Trim().Trim('"'); +string outputFolder = string.IsNullOrEmpty(outputInput) + ? Path.GetDirectoryName(pdfPath) ?? "." + : outputInput; + +Directory.CreateDirectory(outputFolder); + +string fileNameWithoutExt = Path.GetFileNameWithoutExtension(pdfPath); + +Console.WriteLine($"Konverterer: {pdfPath}"); + +using var docReader = DocLib.Instance.GetDocReader(pdfPath, new PageDimensions(1080, 1920)); + +int pageCount = docReader.GetPageCount(); +Console.WriteLine($"Antal sider: {pageCount}"); + +for (int i = 0; i < pageCount; i++) +{ + using var pageReader = docReader.GetPageReader(i); + + int width = pageReader.GetPageWidth(); + int height = pageReader.GetPageHeight(); + byte[] rawBytes = pageReader.GetImage(); + + string outputPath = Path.Combine(outputFolder, $"{fileNameWithoutExt}_side_{i + 1}.png"); + + SaveAsPng(rawBytes, width, height, outputPath); + + Console.WriteLine($"Gemt: {outputPath}"); +} + +Console.WriteLine("Konvertering fuldført!"); +Console.WriteLine(); +Console.WriteLine("Tryk på en tast for at afslutte..."); +Console.ReadKey(); + +static void SaveAsPng(byte[] bgraData, int width, int height, string outputPath) +{ + using var fileStream = File.Create(outputPath); + using var bw = new BinaryWriter(fileStream); + + // PNG Signature + bw.Write(new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }); + + // IHDR chunk + byte[] ihdrData; + using (var ms = new MemoryStream()) + { + WriteBigEndianInt(ms, width); + WriteBigEndianInt(ms, height); + ms.WriteByte(8); // bit depth + ms.WriteByte(6); // color type (RGBA) + ms.WriteByte(0); // compression + ms.WriteByte(0); // filter + ms.WriteByte(0); // interlace + ihdrData = ms.ToArray(); + } + WriteChunk(bw, "IHDR", ihdrData); + + // Byg uncompressed data (filter-byte + RGBA pixels per scanline) + int scanlineSize = 1 + width * 4; // 1 filter byte + RGBA + byte[] uncompressed = new byte[height * scanlineSize]; + + for (int y = 0; y < height; y++) + { + int rowStart = y * scanlineSize; + uncompressed[rowStart] = 0; // filter type: none + + for (int x = 0; x < width; x++) + { + int srcIdx = (y * width + x) * 4; + int dstIdx = rowStart + 1 + x * 4; + + // BGRA til RGBA + uncompressed[dstIdx + 0] = bgraData[srcIdx + 2]; // R + uncompressed[dstIdx + 1] = bgraData[srcIdx + 1]; // G + uncompressed[dstIdx + 2] = bgraData[srcIdx + 0]; // B + uncompressed[dstIdx + 3] = bgraData[srcIdx + 3]; // A + } + } + + // Komprimer med Deflate + using var compressedStream = new MemoryStream(); + using (var deflate = new System.IO.Compression.DeflateStream(compressedStream, System.IO.Compression.CompressionLevel.Fastest, true)) + { + deflate.Write(uncompressed); + } + byte[] compressed = compressedStream.ToArray(); + + // Beregn Adler-32 på uncompressed data + uint adler = Adler32(uncompressed); + + // Byg zlib stream: header + compressed + adler32 + using var zlibStream = new MemoryStream(); + zlibStream.WriteByte(0x78); // CMF + zlibStream.WriteByte(0x01); // FLG + zlibStream.Write(compressed); + WriteBigEndianUInt(zlibStream, adler); + + WriteChunk(bw, "IDAT", zlibStream.ToArray()); + + // IEND chunk + WriteChunk(bw, "IEND", Array.Empty()); +} + +static void WriteChunk(BinaryWriter bw, string type, byte[] data) +{ + byte[] lengthBytes = BitConverter.GetBytes(data.Length); + if (BitConverter.IsLittleEndian) Array.Reverse(lengthBytes); + bw.Write(lengthBytes); + + byte[] typeBytes = System.Text.Encoding.ASCII.GetBytes(type); + bw.Write(typeBytes); + bw.Write(data); + + // CRC32 + byte[] crcData = new byte[4 + data.Length]; + Array.Copy(typeBytes, crcData, 4); + Array.Copy(data, 0, crcData, 4, data.Length); + uint crc = Crc32(crcData); + byte[] crcBytes = BitConverter.GetBytes(crc); + if (BitConverter.IsLittleEndian) Array.Reverse(crcBytes); + bw.Write(crcBytes); +} + +static void WriteBigEndianInt(Stream stream, int value) +{ + byte[] bytes = BitConverter.GetBytes(value); + if (BitConverter.IsLittleEndian) Array.Reverse(bytes); + stream.Write(bytes); +} + +static void WriteBigEndianUInt(Stream stream, uint value) +{ + byte[] bytes = BitConverter.GetBytes(value); + if (BitConverter.IsLittleEndian) Array.Reverse(bytes); + stream.Write(bytes); +} + +static uint Crc32(byte[] data) +{ + uint crc = 0xFFFFFFFF; + foreach (byte b in data) + { + crc ^= b; + for (int i = 0; i < 8; i++) + crc = (crc >> 1) ^ (0xEDB88320 & ~((crc & 1) - 1)); + } + return ~crc; +} + +static uint Adler32(byte[] data) +{ + uint a = 1, b = 0; + foreach (byte d in data) + { + a = (a + d) % 65521; + b = (b + a) % 65521; + } + return (b << 16) | a; +}