Browse Source

Add CUPS PPD parsing for advanced paper size detection on macOS

- Implement `CupsPaperQuery` to query supported paper sizes from CUPS printers, leveraging PPD files.
- Update `MacPrintEngine` to normalize paper names and integrate new paper size detection logic.
- Replace `PaperSizeHelper` fallback with prioritized PPD-based querying in `GetPaperSizes`.
- Enhance print initialization to populate paper sizes dynamically from CUPS or fallback when unavailable.
Ruben 1 week ago
parent
commit
4cd098ca39

+ 34 - 7
src/PicView.Avalonia.MacOS/Printing/MacPrintEngine.cs

@@ -97,14 +97,41 @@ public static class MacPrintEngine
     {
         requestedName ??= "A4";
 
-        // Pull width/height from helper (in mm)
-        var (widthMm, heightMm) = PaperSizeHelper.GetMmSize(requestedName);
+        // Convert from CUPS naming patterns to PaperSizeHelper known names
+        // Examples:
+        //   "iso_a4_210x297mm"  → "A4"
+        //   "A4.Borderless"     → "A4"
+        //   "na_letter_8.5x11in"→ "Letter"
 
-        var isLandscape = orientation == (int)Orientations.Landscape;
+        var normalized = NormalizePaperName(requestedName);
 
-        return isLandscape
-            ? new PaperInfo(requestedName, heightMm, widthMm)   // swap
-            : new PaperInfo(requestedName, widthMm, heightMm);
+        var (w, h) = PaperSizeHelper.GetMmSize(normalized);
+
+        var landscape = orientation == (int)Orientations.Landscape;
+
+        return landscape
+            ? new PaperInfo(normalized, h, w)
+            : new PaperInfo(normalized, w, h);
+    }
+
+    private static string NormalizePaperName(string cupsName)
+    {
+        cupsName = cupsName.ToLowerInvariant();
+
+        return cupsName switch
+        {
+            _ when cupsName.Contains("a4") => "A4",
+            _ when cupsName.Contains("a3") => "A3",
+            _ when cupsName.Contains("a5") => "A5",
+            _ when cupsName.Contains("letter") => "Letter",
+            _ when cupsName.Contains("legal") => "Legal",
+            _ when cupsName.Contains("tabloid") => "Tabloid",
+            _ when cupsName.Contains("4x6") => "4x6",
+            _ when cupsName.Contains("5x7") => "5x7",
+            _ when cupsName.Contains("8x10") => "8x10",
+            // unknown → fallback
+            _ => cupsName
+        };
     }
 
     private static async ValueTask<string> SaveToTempPng(RenderTargetBitmap rtb)
@@ -119,7 +146,7 @@ public static class MacPrintEngine
     }
 
     public static IEnumerable<string> GetPaperSizes(string printerName)
-        => PaperSizeHelper.GetAllNames();
+        => CupsPaperQuery.GetPaperSizes(printerName);
 
     public readonly record struct PaperInfo(string Name, double WidthMm, double HeightMm);
 }

+ 2 - 2
src/PicView.Avalonia.MacOS/Printing/MacPrintInitialization.cs

@@ -26,9 +26,9 @@ public static class MacPrintInitialization
 
         var defaultPrinter = printers.FirstOrDefault() ?? string.Empty;
 
-        // 2. Paper sizes – simple catalog (A4 / Letter)
+        // 2. Paper sizes - from printer or fallback
         vm.PrintPreview.PaperSizes.Value =
-            new List<string>(MacPrintEngine.GetPaperSizes(defaultPrinter));
+            CupsPaperQuery.GetPaperSizes(defaultPrinter).ToList();
 
         // 3. Build initial PrintSettings
         var currentPrintSettings = new PrintSettings

+ 33 - 0
src/PicView.Core.MacOS/NativeMethods.cs

@@ -55,5 +55,38 @@ internal static partial class NativeMethods
     [UnmanagedCallConv(CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])]
     internal static partial int cupsPrintFile(string name, string filename, string title, int num_options, IntPtr options);
     
+    [LibraryImport(LibCups, StringMarshalling = StringMarshalling.Utf16)]
+    [UnmanagedCallConv(CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])]
+    internal static partial IntPtr cupsGetPPD(string printerName);
+
+    [LibraryImport(LibCups, StringMarshalling = StringMarshalling.Utf16)]
+    [UnmanagedCallConv(CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])]
+    internal static partial IntPtr ppdOpenFile(string filename);
+
+    [LibraryImport(LibCups)]
+    [UnmanagedCallConv(CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])]
+    internal static partial void ppdClose(IntPtr ppd);
+
+    [StructLayout(LayoutKind.Sequential)]
+    internal struct ppd_size_t
+    {
+        public IntPtr name;     // char*
+        public float width;     // PostScript points (1/72 inch)
+        public float length;
+        public float left;
+        public float bottom;
+        public float right;
+        public float top;
+    }
+
+    [StructLayout(LayoutKind.Sequential)]
+    internal struct ppd_file_t
+    {
+        public IntPtr filename;
+        public IntPtr name;
+        public int num_sizes;
+        public IntPtr sizes; // ppd_size_t*
+    }
+    
     #endregion
 }

+ 73 - 0
src/PicView.Core.MacOS/Printing/CupsPaperQuery.cs

@@ -0,0 +1,73 @@
+using System.Runtime.InteropServices;
+using PicView.Core.Printing;
+
+namespace PicView.Core.MacOS.Printing;
+
+public static partial class CupsPaperQuery
+{
+    // ─────────────────────────────────────────────────────────────────────────────
+    //   PUBLIC: Query supported sizes
+    // ─────────────────────────────────────────────────────────────────────────────
+
+    public static IEnumerable<string> GetPaperSizes(string printerName)
+    {
+        // Try PPD → fallback → PaperSizeHelper
+        foreach (var ppdSize in TryGetSizesFromPpd(printerName))
+        {
+            yield return ppdSize;
+        }
+        
+        // If PPD fails → fallback to PaperSizeHelper
+        if (TryGetSizesFromPpd(printerName).Any())
+        {
+            yield break;
+        }
+
+        foreach (var name in PaperSizeHelper.GetAllNames())
+        {
+            yield return name;
+        }
+    }
+
+
+    // ─────────────────────────────────────────────────────────────────────────────
+    //   PPD PARSING
+    // ─────────────────────────────────────────────────────────────────────────────
+
+    private static IEnumerable<string> TryGetSizesFromPpd(string printerName)
+    {
+        var ppdFilePtr = NativeMethods.cupsGetPPD(printerName);
+        if (ppdFilePtr == IntPtr.Zero)
+            yield break;
+
+        var path = Marshal.PtrToStringAnsi(ppdFilePtr);
+        if (string.IsNullOrWhiteSpace(path))
+            yield break;
+
+        var ppdPtr = NativeMethods.ppdOpenFile(path);
+        if (ppdPtr == IntPtr.Zero)
+            yield break;
+
+        try
+        {
+            var ppd = Marshal.PtrToStructure<NativeMethods.ppd_file_t>(ppdPtr);
+            var sizePtr = ppd.sizes;
+            var count = ppd.num_sizes;
+            var entrySize = Marshal.SizeOf<NativeMethods.ppd_size_t>();
+
+            for (var i = 0; i < count; i++)
+            {
+                var entryPtr = IntPtr.Add(sizePtr, i * entrySize);
+                var entry = Marshal.PtrToStructure<NativeMethods.ppd_size_t>(entryPtr);
+
+                var name = Marshal.PtrToStringAnsi(entry.name);
+                if (!string.IsNullOrWhiteSpace(name))
+                    yield return name;
+            }
+        }
+        finally
+        {
+            NativeMethods.ppdClose(ppdPtr);
+        }
+    }
+}