Procházet zdrojové kódy

Add advanced interop generation prototype (does not work, general structure)

Marcin Ziąbek před 1 měsícem
rodič
revize
fdb8b5f00a

+ 29 - 0
Source/QuestPDF.Companion.TestRunner/Program.cs

@@ -8,6 +8,35 @@ using QuestPDF.ReportSample.Layouts;
 
 Settings.License = LicenseType.Professional;
 
+var sw = System.Diagnostics.Stopwatch.StartNew();
+
+foreach (var i in Enumerable.Range(0, 1000))
+{
+    Document
+        .Create(document =>
+        {
+            foreach (var j in Enumerable.Range(0, 20))
+            {
+                document.Page(page =>
+                {
+                    page.Margin(50);
+
+                    page.Content().Text(text =>
+                    {
+                        text.Span("Hello world from ");
+                        text.Span(j.ToString()).FontColor(Colors.Red.Medium);
+                        text.Span(" iteration!");
+                    });
+                });
+            }
+        })
+        .GeneratePdf();
+}
+
+sw.Stop();
+Console.WriteLine($"Total time for 1000 documents: {sw.ElapsedMilliseconds} ms");
+return;
+
 //await RunGenericException();
 //await RunLayoutError();
 await RunSimpleDocument();

+ 32 - 2
Source/QuestPDF.InteropGenerators/CSharpInteropGenerator.cs

@@ -77,14 +77,24 @@ public static class CSharpInteropGenerator
         }
         
         var methodName = GenerateMethodName(method);
-        var callTarget = $"{method.ContainingType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}.{method.Name}";
+        var isExtensionMethod = method.IsExtensionMethod;
+        var isInstanceMethod = !method.IsStatic && !isExtensionMethod;
         
         // Determine interop signature (reference types become nint handles)
         var interopReturnType = PublicApiAnalyzer.IsReferenceType(method.ReturnType) 
             ? "nint" 
             : method.ReturnType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
         
-        var interopParameters = string.Join(", ", method.Parameters.Select(p =>
+        // For instance methods, add 'this' parameter as first parameter
+        var interopParametersList = new List<string>();
+        
+        if (isInstanceMethod)
+        {
+            // Instance methods need the 'this' object as first parameter
+            interopParametersList.Add("nint @this");
+        }
+        
+        interopParametersList.AddRange(method.Parameters.Select(p =>
         {
             var paramType = PublicApiAnalyzer.IsReferenceType(p.Type) 
                 ? "nint" 
@@ -92,10 +102,19 @@ public static class CSharpInteropGenerator
             return $"{paramType} {p.Name}";
         }));
         
+        var interopParameters = string.Join(", ", interopParametersList);
+        
         sb.AppendLine($"    [UnmanagedCallersOnly(EntryPoint = \"{GenerateEntryPointName(method)}\", CallConvs = new[] {{ typeof(CallConvCdecl) }})]");
         sb.AppendLine($"    public static {interopReturnType} {methodName}({interopParameters})");
         sb.AppendLine("    {");
         
+        // For instance methods, unbox the 'this' parameter
+        if (isInstanceMethod)
+        {
+            var containingType = method.ContainingType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
+            sb.AppendLine($"        var this_obj = UnboxHandle<{containingType}>(@this);");
+        }
+        
         // Unbox reference type parameters
         foreach (var param in method.Parameters)
         {
@@ -110,6 +129,17 @@ public static class CSharpInteropGenerator
         var arguments = string.Join(", ", method.Parameters.Select(p => 
             PublicApiAnalyzer.IsReferenceType(p.Type) ? $"{p.Name}_obj" : p.Name));
         
+        // Build the call target
+        string callTarget;
+        if (isInstanceMethod)
+        {
+            callTarget = $"this_obj.{method.Name}";
+        }
+        else
+        {
+            callTarget = $"{method.ContainingType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}.{method.Name}";
+        }
+        
         // Call the method and handle the result
         if (method.ReturnsVoid)
         {

+ 93 - 0
Source/QuestPDF.InteropGenerators/PublicApiAnalyzer.cs

@@ -19,6 +19,27 @@ public static class PublicApiAnalyzer
         return methods;
     }
 
+    /// <summary>
+    /// Collects all public methods from Fluent API classes (descriptors, configurations, handlers, etc.)
+    /// </summary>
+    public static List<IMethodSymbol> CollectFluentApiMethods(INamespaceSymbol rootNamespace)
+    {
+        var methods = new List<IMethodSymbol>();
+        CollectFluentApiMethodsRecursive(rootNamespace, methods);
+        return methods;
+    }
+
+    /// <summary>
+    /// Collects all interop-eligible methods: extension methods + fluent API class methods
+    /// </summary>
+    public static List<IMethodSymbol> CollectAllInteropMethods(INamespaceSymbol rootNamespace)
+    {
+        var methods = new List<IMethodSymbol>();
+        CollectExtensionMethodsRecursive(rootNamespace, methods);
+        CollectFluentApiMethodsRecursive(rootNamespace, methods);
+        return methods;
+    }
+
     private static void CollectExtensionMethodsRecursive(INamespaceSymbol ns, List<IMethodSymbol> methods)
     {
         foreach (var member in ns.GetMembers())
@@ -54,6 +75,78 @@ public static class PublicApiAnalyzer
         }
     }
 
+    private static void CollectFluentApiMethodsRecursive(INamespaceSymbol ns, List<IMethodSymbol> methods)
+    {
+        foreach (var member in ns.GetMembers())
+        {
+            if (member is INamespaceSymbol childNs)
+            {
+                CollectFluentApiMethodsRecursive(childNs, methods);
+            }
+            else if (member is INamedTypeSymbol type)
+            {
+                CollectFluentApiMethodsFromType(type, methods);
+            }
+        }
+    }
+
+    private static void CollectFluentApiMethodsFromType(INamedTypeSymbol type, List<IMethodSymbol> methods)
+    {
+        if (type.DeclaredAccessibility != Accessibility.Public || type.IsImplicitlyDeclared)
+            return;
+
+        // Check if this is a Fluent API class (descriptors, configurations, handlers, etc.)
+        if (!IsFluentApiClass(type))
+            return;
+
+        foreach (var member in type.GetMembers())
+        {
+            // Collect public instance methods (not extension methods, not constructors, not property accessors)
+            if (member is IMethodSymbol { MethodKind: MethodKind.Ordinary, IsExtensionMethod: false, DeclaredAccessibility: Accessibility.Public, IsStatic: false } method 
+                && !method.IsImplicitlyDeclared)
+            {
+                methods.Add(method);
+            }
+        }
+
+        // Process nested types
+        foreach (var nestedType in type.GetTypeMembers())
+        {
+            CollectFluentApiMethodsFromType(nestedType, methods);
+        }
+    }
+
+    /// <summary>
+    /// Determines if a type is part of the Fluent API (descriptors, configurations, handlers, etc.)
+    /// </summary>
+    private static bool IsFluentApiClass(INamedTypeSymbol type)
+    {
+        var typeName = type.Name;
+        var namespaceName = type.ContainingNamespace?.ToDisplayString() ?? "";
+
+        // Check if it's in the Fluent namespace
+        if (namespaceName.Contains("QuestPDF.Fluent"))
+            return true;
+
+        // Check for common Fluent API naming patterns
+        if (typeName.EndsWith("Descriptor") || 
+            typeName.EndsWith("Configuration") || 
+            typeName.EndsWith("Handler") ||
+            typeName.EndsWith("Builder") ||
+            typeName.EndsWith("Settings"))
+            return true;
+
+        // Check if the type implements IContainer or IDocumentContainer
+        foreach (var iface in type.AllInterfaces)
+        {
+            var ifaceName = iface.ToDisplayString();
+            if (ifaceName.Contains("IContainer") || ifaceName.Contains("IDocumentContainer"))
+                return true;
+        }
+
+        return false;
+    }
+
     /// <summary>
     /// Checks if a method is supported for interop generation
     /// </summary>

+ 7 - 4
Source/QuestPDF.InteropGenerators/PublicApiGenerator.cs

@@ -13,15 +13,18 @@ public sealed class PublicApiGenerator : IIncrementalGenerator
     {
         context.RegisterSourceOutput(context.CompilationProvider, static (spc, compilation) =>
         {
-            // Step 1: Analyze the public API and collect extension methods
-            var extensionMethods = PublicApiAnalyzer.CollectExtensionMethods(compilation.Assembly.GlobalNamespace);
+            // Step 1: Analyze the public API and collect all interop methods
+            // This includes both extension methods AND public methods from Fluent API classes
+            // (descriptors, configurations, handlers, etc.)
+            var allMethods = PublicApiAnalyzer.CollectAllInteropMethods(compilation.Assembly.GlobalNamespace);
             
             // Step 2: Generate C# UnmanagedCallersOnly interop code
-            var csharpInteropCode = CSharpInteropGenerator.GenerateInteropCode(extensionMethods);
+            var csharpInteropCode = CSharpInteropGenerator.GenerateInteropCode(allMethods);
             spc.AddSource("GeneratedInterop.g.cs", csharpInteropCode);
             
             // Step 3: Generate Python ctypes bindings
-            var pythonBindingsCode = PythonBindingsGenerator.GeneratePythonBindings(extensionMethods);
+            // Python bindings strictly follow all C# interop functionalities
+            var pythonBindingsCode = PythonBindingsGenerator.GeneratePythonBindings(allMethods);
             // Note: Python file is added as .txt so it appears in generated files
             // Extract and rename to .py for actual use
             spc.AddSource("GeneratedInterop.g.py.txt", pythonBindingsCode);

+ 165 - 50
Source/QuestPDF.InteropGenerators/PythonBindingsGenerator.cs

@@ -17,20 +17,85 @@ public static class PythonBindingsGenerator
     {
         var sb = new StringBuilder();
         
+        // Generate header and base classes
+        GeneratePythonHeader(sb);
+        
+        // Group methods by their containing type
+        var methodsByType = GroupMethodsByType(extensionMethods);
+        
+        // Generate the main library class
+        GenerateLibraryClass(sb, extensionMethods);
+        
+        // Generate Python wrapper classes for each C# type
+        GeneratePythonWrapperClasses(sb, methodsByType);
+        
+        // Comment out all lines with "//" to avoid C# compilation issues
+        return CommentOutPythonCode(sb.ToString());
+    }
+    
+    /// <summary>
+    /// Comments out all Python code lines with "//" prefix to avoid C# compilation issues
+    /// </summary>
+    private static string CommentOutPythonCode(string pythonCode)
+    {
+        var lines = pythonCode.Split(new[] { "\r\n", "\r", "\n" }, System.StringSplitOptions.None);
+        var commentedLines = lines.Select(line => "// " + line);
+        return string.Join("\n", commentedLines);
+    }
+    
+    private static void GeneratePythonHeader(StringBuilder sb)
+    {
         sb.AppendLine("# Auto-generated Python bindings for QuestPDF");
         sb.AppendLine("# This file provides ctypes-based wrapper for the QuestPDF interop layer");
         sb.AppendLine();
         sb.AppendLine("import ctypes");
         sb.AppendLine("import platform");
-        sb.AppendLine("from typing import Optional");
+        sb.AppendLine("from typing import Optional, TYPE_CHECKING");
         sb.AppendLine("from pathlib import Path");
         sb.AppendLine();
+        sb.AppendLine("if TYPE_CHECKING:");
+        sb.AppendLine("    from typing import Any");
+        sb.AppendLine();
         sb.AppendLine();
         sb.AppendLine("class QuestPDFException(Exception):");
         sb.AppendLine("    \"\"\"Base exception for QuestPDF errors\"\"\"");
         sb.AppendLine("    pass");
         sb.AppendLine();
         sb.AppendLine();
+    }
+    
+    private static Dictionary<string, List<IMethodSymbol>> GroupMethodsByType(List<IMethodSymbol> methods)
+    {
+        var result = new Dictionary<string, List<IMethodSymbol>>();
+        
+        foreach (var method in methods)
+        {
+            if (!PublicApiAnalyzer.IsSupported(method))
+                continue;
+            
+            // For extension methods, use the first parameter type (the extended type)
+            // For instance methods, use the containing type
+            string typeName;
+            if (method.IsExtensionMethod)
+            {
+                typeName = method.Parameters[0].Type.Name;
+            }
+            else
+            {
+                typeName = method.ContainingType.Name;
+            }
+            
+            if (!result.ContainsKey(typeName))
+                result[typeName] = new List<IMethodSymbol>();
+            
+            result[typeName].Add(method);
+        }
+        
+        return result;
+    }
+    
+    private static void GenerateLibraryClass(StringBuilder sb, List<IMethodSymbol> extensionMethods)
+    {
         sb.AppendLine("class QuestPDFLibrary:");
         sb.AppendLine("    \"\"\"Wrapper for QuestPDF native library\"\"\"");
         sb.AppendLine("    ");
@@ -95,54 +160,72 @@ public static class PythonBindingsGenerator
         sb.AppendLine("        if handle != 0:");
         sb.AppendLine("            self._lib.questpdf_free_handle(handle)");
         sb.AppendLine();
+        sb.AppendLine();
+    }
+    
+    private static void GeneratePythonWrapperClasses(StringBuilder sb, Dictionary<string, List<IMethodSymbol>> methodsByType)
+    {
+        // Sort types alphabetically for consistent output
+        var sortedTypes = methodsByType.Keys.OrderBy(k => k).ToList();
         
-        // Generate Python wrapper methods
-        foreach (var method in extensionMethods)
+        foreach (var typeName in sortedTypes)
         {
-            if (!PublicApiAnalyzer.IsSupported(method))
-                continue;
-                
-            GeneratePythonWrapperMethod(sb, method);
+            var methods = methodsByType[typeName];
+            var pythonClassName = ToPythonClassName(typeName);
+            
+            sb.AppendLine($"class {pythonClassName}:");
+            sb.AppendLine($"    \"\"\"Python wrapper for {typeName}\"\"\"");
+            sb.AppendLine("    ");
+            sb.AppendLine("    def __init__(self, lib: QuestPDFLibrary, handle: int):");
+            sb.AppendLine("        self._lib = lib");
+            sb.AppendLine("        self._handle = handle");
+            sb.AppendLine("    ");
+            sb.AppendLine("    @property");
+            sb.AppendLine("    def handle(self) -> int:");
+            sb.AppendLine("        \"\"\"Get the underlying native handle\"\"\"");
+            sb.AppendLine("        return self._handle");
+            sb.AppendLine("    ");
+            sb.AppendLine("    def __del__(self):");
+            sb.AppendLine("        if hasattr(self, '_handle') and self._handle != 0:");
+            sb.AppendLine("            try:");
+            sb.AppendLine("                self._lib.free_handle(self._handle)");
+            sb.AppendLine("            except:");
+            sb.AppendLine("                pass  # Ignore errors during cleanup");
+            sb.AppendLine("    ");
+            sb.AppendLine("    def __enter__(self):");
+            sb.AppendLine("        return self");
+            sb.AppendLine("    ");
+            sb.AppendLine("    def __exit__(self, exc_type, exc_val, exc_tb):");
+            sb.AppendLine("        self._lib.free_handle(self._handle)");
+            sb.AppendLine("        self._handle = 0");
+            sb.AppendLine();
+            
+            // Generate methods for this class
+            foreach (var method in methods.OrderBy(m => m.Name))
+            {
+                GeneratePythonClassMethod(sb, method, pythonClassName);
+            }
+            
+            sb.AppendLine();
         }
-        
-        sb.AppendLine();
-        sb.AppendLine();
-        sb.AppendLine("class Handle:");
-        sb.AppendLine("    \"\"\"Wrapper for a managed object handle with automatic cleanup\"\"\"");
-        sb.AppendLine("    ");
-        sb.AppendLine("    def __init__(self, lib: QuestPDFLibrary, handle: int):");
-        sb.AppendLine("        self._lib = lib");
-        sb.AppendLine("        self._handle = handle");
-        sb.AppendLine("    ");
-        sb.AppendLine("    @property");
-        sb.AppendLine("    def value(self) -> int:");
-        sb.AppendLine("        return self._handle");
-        sb.AppendLine("    ");
-        sb.AppendLine("    def __del__(self):");
-        sb.AppendLine("        if hasattr(self, '_handle') and self._handle != 0:");
-        sb.AppendLine("            try:");
-        sb.AppendLine("                self._lib.free_handle(self._handle)");
-        sb.AppendLine("            except:");
-        sb.AppendLine("                pass  # Ignore errors during cleanup");
-        sb.AppendLine("    ");
-        sb.AppendLine("    def __enter__(self):");
-        sb.AppendLine("        return self");
-        sb.AppendLine("    ");
-        sb.AppendLine("    def __exit__(self, exc_type, exc_val, exc_tb):");
-        sb.AppendLine("        self._lib.free_handle(self._handle)");
-        sb.AppendLine("        self._handle = 0");
-        
-        return sb.ToString();
     }
     
     private static void GeneratePythonFunctionSetup(StringBuilder sb, IMethodSymbol method)
     {
         var entryPoint = CSharpInteropGenerator.GenerateEntryPointName(method);
+        var isInstanceMethod = !method.IsStatic && !method.IsExtensionMethod;
         
         sb.AppendLine($"        # {method.ContainingType.Name}.{method.Name}");
         sb.Append($"        self._lib.{entryPoint}.argtypes = [");
         
         var argTypes = new List<string>();
+        
+        // For instance methods, add 'this' parameter
+        if (isInstanceMethod)
+        {
+            argTypes.Add("ctypes.c_void_p");
+        }
+        
         foreach (var param in method.Parameters)
         {
             argTypes.Add(GetPythonCType(param.Type));
@@ -154,35 +237,48 @@ public static class PythonBindingsGenerator
         sb.AppendLine();
     }
     
-    private static void GeneratePythonWrapperMethod(StringBuilder sb, IMethodSymbol method)
+    private static void GeneratePythonClassMethod(StringBuilder sb, IMethodSymbol method, string pythonClassName)
     {
         var entryPoint = CSharpInteropGenerator.GenerateEntryPointName(method);
         var pythonName = ToPythonMethodName(method.Name);
+        var isExtensionMethod = method.IsExtensionMethod;
         
         // Build parameter list
         var parameters = new List<string> { "self" };
-        foreach (var param in method.Parameters)
+        
+        // For extension methods, skip the first parameter (the extended type - that's 'self')
+        var paramsToProcess = isExtensionMethod ? method.Parameters.Skip(1) : method.Parameters;
+        
+        foreach (var param in paramsToProcess)
         {
             var paramName = ToPythonParamName(param.Name);
-            var pythonType = GetPythonTypeHint(param.Type);
+            var pythonType = GetPythonTypeHint(param.Type, isParameter: true);
             parameters.Add($"{paramName}: {pythonType}");
         }
         
-        var returnTypeHint = GetPythonTypeHint(method.ReturnType);
+        var returnTypeHint = GetPythonTypeHint(method.ReturnType, isParameter: false);
         
-        sb.AppendLine($"    def {pythonName}({string.Join(", ", parameters)}) -> {returnTypeHint}:");
+        sb.AppendLine($"    def {pythonName}({string.Join(", ", parameters)}) -> '{returnTypeHint}':");
         sb.AppendLine($"        \"\"\"");
-        sb.AppendLine($"        {method.ContainingType.Name}.{method.Name}");
+        sb.AppendLine($"        {method.Name}");
+        if (!string.IsNullOrEmpty(method.GetDocumentationCommentXml()))
+        {
+            // Could extract summary from XML here if needed
+        }
         sb.AppendLine($"        \"\"\"");
         
         // Build argument list for the call
         var callArgs = new List<string>();
-        foreach (var param in method.Parameters)
+        
+        // Always pass 'self._handle' as the first argument (either for extension method or instance method)
+        callArgs.Add("self._handle");
+        
+        foreach (var param in paramsToProcess)
         {
             var paramName = ToPythonParamName(param.Name);
             if (PublicApiAnalyzer.IsReferenceType(param.Type))
             {
-                callArgs.Add($"{paramName}.value if isinstance({paramName}, Handle) else {paramName}");
+                callArgs.Add($"{paramName}.handle if hasattr({paramName}, 'handle') else {paramName}");
             }
             else
             {
@@ -192,16 +288,17 @@ public static class PythonBindingsGenerator
         
         if (method.ReturnsVoid)
         {
-            sb.AppendLine($"        self._lib.{entryPoint}({string.Join(", ", callArgs)})");
+            sb.AppendLine($"        self._lib._lib.{entryPoint}({string.Join(", ", callArgs)})");
         }
         else if (PublicApiAnalyzer.IsReferenceType(method.ReturnType))
         {
-            sb.AppendLine($"        result = self._lib.{entryPoint}({string.Join(", ", callArgs)})");
-            sb.AppendLine($"        return Handle(self, result)");
+            sb.AppendLine($"        result = self._lib._lib.{entryPoint}({string.Join(", ", callArgs)})");
+            var returnPythonClass = ToPythonClassName(method.ReturnType.Name);
+            sb.AppendLine($"        return {returnPythonClass}(self._lib, result)");
         }
         else
         {
-            sb.AppendLine($"        return self._lib.{entryPoint}({string.Join(", ", callArgs)})");
+            sb.AppendLine($"        return self._lib._lib.{entryPoint}({string.Join(", ", callArgs)})");
         }
         
         sb.AppendLine();
@@ -234,13 +331,16 @@ public static class PythonBindingsGenerator
         };
     }
     
-    private static string GetPythonTypeHint(ITypeSymbol type)
+    private static string GetPythonTypeHint(ITypeSymbol type, bool isParameter)
     {
         if (type.SpecialType == SpecialType.System_Void)
             return "None";
         
         if (PublicApiAnalyzer.IsReferenceType(type))
-            return "Handle";
+        {
+            // Return the appropriate Python class name for reference types
+            return ToPythonClassName(type.Name);
+        }
         
         return type.SpecialType switch
         {
@@ -261,6 +361,21 @@ public static class PythonBindingsGenerator
         };
     }
     
+    private static string ToPythonClassName(string csharpTypeName)
+    {
+        // Convert C# type name to Python class name
+        // Remove generic markers and sanitize
+        var cleanName = csharpTypeName
+            .Replace("`", "")
+            .Replace("<", "_")
+            .Replace(">", "_")
+            .Replace(",", "_")
+            .Trim('_');
+        
+        // Return as-is (PascalCase is acceptable in Python for class names)
+        return cleanName;
+    }
+    
     private static string ToPythonMethodName(string name)
     {
         // Convert PascalCase to snake_case

+ 232 - 7
Source/QuestPDF.InteropGenerators/README.md

@@ -11,18 +11,37 @@ The code is organized into 4 separate, well-defined components:
 
 **Responsibilities:**
 - Collects all public extension methods from the assembly
+- **NEW:** Collects public instance methods from Fluent API classes (descriptors, configurations, handlers, etc.)
 - Determines if a method is supported for interop (checks for async, Task-related types, ref/out parameters, generics)
 - Validates type compatibility (primitives, structs, enums, classes, interfaces)
 - Identifies reference types that need handle boxing
 - Filters out async/await patterns and Task-related types
+- **NEW:** Identifies Fluent API classes by namespace, naming patterns, and interface implementation
 
 **Key Methods:**
 - `CollectExtensionMethods()` - Recursively scans namespaces for public extension methods
+- **NEW:** `CollectFluentApiMethods()` - Collects public methods from Fluent API classes
+- **NEW:** `CollectAllInteropMethods()` - Collects both extension methods AND Fluent API class methods
+- **NEW:** `IsFluentApiClass()` - Determines if a class is part of the Fluent API (checks for Descriptor/Configuration/Handler/Builder suffixes, QuestPDF.Fluent namespace, IContainer interfaces)
 - `IsSupported()` - Validates if a method can be exposed via interop
 - `IsSupportedType()` - Checks if a type is compatible with UnmanagedCallersOnly
 - `IsReferenceType()` - Identifies classes/interfaces that need handle boxing
 - `IsTaskRelatedType()` - Detects Task, CancellationToken, and async-related types
 
+**Fluent API Class Detection:**
+The analyzer now automatically detects Fluent API classes using multiple strategies:
+1. **Namespace-based:** Classes in `QuestPDF.Fluent` namespace
+2. **Naming patterns:** Classes ending with `Descriptor`, `Configuration`, `Handler`, `Builder`, or `Settings`
+3. **Interface-based:** Classes implementing `IContainer` or `IDocumentContainer`
+
+**Examples of detected Fluent API classes:**
+- `TextSpanDescriptor`, `TextPageNumberDescriptor`, `TextBlockDescriptor`, `TextDescriptor`
+- `TableColumnsDefinitionDescriptor`, `TableCellDescriptor`, `TableDescriptor`
+- `PageDescriptor`, `DecorationDescriptor`, `LayersDescriptor`, `GridDescriptor`
+- `ColumnDescriptor`, `RowDescriptor`, `InlinedDescriptor`, `MultiColumnDescriptor`
+- `DocumentOperation.LayerConfiguration`, `DocumentOperation.DocumentAttachment`
+- And many more...
+
 ### 2. CSharpInteropGenerator.cs
 **Purpose:** Generates C# UnmanagedCallersOnly bindings for native interop.
 
@@ -30,28 +49,147 @@ The code is organized into 4 separate, well-defined components:
 - Creates `GeneratedInterop.g.cs` with all interop methods
 - Generates helper methods for handle boxing/unboxing (BoxHandle, UnboxHandle, FreeHandle)
 - Converts managed method calls to UnmanagedCallersOnly exports
+- **NEW:** Handles both extension methods and instance methods from Fluent API classes
 - Handles parameter marshalling (reference types → nint handles)
+- **NEW:** For instance methods, automatically adds 'this' parameter as first nint handle
 - Generates entry point names (e.g., `questpdf_fluent_alignmentextensions_alignright`)
 
 **Key Methods:**
 - `GenerateInteropCode()` - Creates the complete C# interop file
 - `GenerateEntryPointName()` - Creates consistent entry point naming
-- `GenerateInteropMethod()` - Generates individual method wrappers
+- `GenerateInteropMethod()` - **UPDATED:** Generates wrappers for both extension and instance methods
 
 **Generated Features:**
 - UnmanagedCallersOnly methods with CDecl calling convention
 - Automatic boxing/unboxing for reference types
 - Support for primitives, structs, enums, classes, and interfaces
 - Memory management via GCHandle
+- **NEW:** Instance method support: automatically unboxes 'this' parameter and calls instance methods
+- **NEW:** Distinguishes between static extension methods and instance methods from descriptor classes
+
+**Example for Extension Method:**
+```csharp
+// Original: public static IContainer AlignCenter(this IContainer container)
+[UnmanagedCallersOnly(EntryPoint = "questpdf_fluent_alignmentextensions_aligncenter")]
+public static nint AlignmentExtensions_AlignCenter(nint container)
+{
+    var container_obj = UnboxHandle<IContainer>(container);
+    var result = QuestPDF.Fluent.AlignmentExtensions.AlignCenter(container_obj);
+    return BoxHandle(result);
+}
+```
+
+**Example for Instance Method:**
+```csharp
+// Original: public TextBlockDescriptor AlignCenter() (instance method)
+[UnmanagedCallersOnly(EntryPoint = "questpdf_fluent_textblockdescriptor_aligncenter")]
+public static nint TextBlockDescriptor_AlignCenter(nint @this)
+{
+    var this_obj = UnboxHandle<TextBlockDescriptor>(@this);
+    var result = this_obj.AlignCenter();
+    return BoxHandle(result);
+}
+```
 
 ### 3. PythonBindingsGenerator.cs
-**Purpose:** Generates Python ctypes bindings for the interop layer.
+**Purpose:** Generates Python ctypes bindings for the interop layer with proper class organization.
 
 **Responsibilities:**
 - Creates `GeneratedInterop.g.py.txt` with Python wrapper classes
+- **NEW:** Organizes methods into separate Python classes matching C# types (PageDescriptor, ColumnDescriptor, IContainer, etc.)
 - Generates ctypes function signatures for all exported methods
+- **NEW:** Each C# type gets its own Python class with appropriate methods
+- **NEW:** Extension methods and instance methods are unified in their respective Python classes
+- Handles both extension methods and instance methods with proper parameter handling
+
+**Key Features:**
+- **Class-based architecture:** Instead of all methods on a single `Handle` or `QuestPDFLibrary` class, methods are organized by type
+- **Automatic type mapping:** C# types like `IContainer`, `PageDescriptor`, etc. map to Python classes `IContainer`, `PageDescriptor`
+- **Fluent API preservation:** Method chaining works naturally in Python as methods return the appropriate typed objects
+- **Smart parameter handling:** Extension methods automatically use `self` for the extended type parameter
+
+**Key Methods:**
+- `GeneratePythonBindings()` - Orchestrates the complete Python generation process
+- `GroupMethodsByType()` - **NEW:** Groups methods by their C# type (extension target or containing type)
+- `GenerateLibraryClass()` - Generates the `QuestPDFLibrary` class that manages the native library
+- `GeneratePythonWrapperClasses()` - **NEW:** Generates separate Python classes for each C# type
+- `GeneratePythonClassMethod()` - **NEW:** Generates methods within their appropriate Python class
+- `ToPythonClassName()` - **NEW:** Converts C# type names to Python class names
+
+**Architecture Example:**
+
+```python
+# Before (all methods on QuestPDFLibrary or Handle):
+lib = QuestPDFLibrary()
+container_handle = lib.page_content(document_handle)
+aligned_handle = lib.align_center(container_handle)
+
+# After (organized by type):
+lib = QuestPDFLibrary()
+page = PageDescriptor(lib, page_handle)
+container = page.content()  # Returns IContainer
+aligned = container.align_center()  # Returns IContainer
+```
+
+**Generated Python Classes:**
+For each C# type (e.g., `PageDescriptor`, `IContainer`, `ColumnDescriptor`), a Python class is generated with:
+- `__init__(self, lib: QuestPDFLibrary, handle: int)` - Constructor accepting library and native handle
+- `handle` property - Access to underlying native handle
+- Context manager support (`__enter__`, `__exit__`) - For automatic cleanup
+- All methods that extend or belong to that type
+- Proper return types (returns instances of the appropriate Python class)
+
+**Complete Usage Example:**
+
+```python
+from questpdf import QuestPDFLibrary
+
+# Initialize the library
+lib = QuestPDFLibrary()
+
+# Create a document
+document = lib.create_document()
+
+# Configure pages using PageDescriptor methods
+page = document.page()
+page.size(PageSize.A4)
+page.margin_left(50)
+page.margin_right(50)
+
+# Get the content container (returns IContainer)
+container = page.content()
+
+# Use IContainer extension methods with fluent chaining
+container = container.padding(20)
+container = container.align_center()
+container = container.background("#FFFFFF")
+
+# Add a column layout (returns ColumnDescriptor)
+column = container.column()
+column.spacing(10)
+
+# Add items to the column
+item1 = column.item()
+item1.text("Hello, World!")
+
+item2 = column.item()
+item2.text("This is generated from Python!")
+
+# Generate the PDF
+document.generate_pdf("output.pdf")
+```
+
+**Key Advantages:**
+
+1. **Type Safety:** Each Python class corresponds to a C# type, making the API intuitive
+2. **IntelliSense Support:** IDEs can provide better autocomplete based on the typed classes
+3. **Method Chaining:** Fluent API patterns work naturally: `container.padding(20).align_center().background("#FFF")`
+4. **Clear Ownership:** Methods belong to their logical types rather than being mixed in a single class
+5. **Extensibility:** New C# types automatically get their own Python classes
+6. **Documentation:** Each class can have type-specific documentation
 - Converts C# types to Python types (nint → Handle, int32 → int, etc.)
 - Creates Python-friendly method names (PascalCase → snake_case)
+- **NEW:** For instance methods, adds 'this_handle' as first parameter
 - Implements automatic handle cleanup with context managers
 
 **Key Methods:**
@@ -59,6 +197,8 @@ The code is organized into 4 separate, well-defined components:
 - `GetPythonCType()` - Maps C# types to ctypes types
 - `GetPythonTypeHint()` - Creates Python type hints for IDE support
 - `ToPythonMethodName()` - Converts PascalCase to snake_case
+- **UPDATED:** `GeneratePythonFunctionSetup()` - Now handles instance methods by adding c_void_p for 'this' parameter
+- **UPDATED:** `GeneratePythonWrapperMethod()` - Now generates proper wrappers for instance methods
 
 **Generated Features:**
 - `QuestPDFLibrary` class - Main wrapper with automatic library discovery
@@ -66,35 +206,120 @@ The code is organized into 4 separate, well-defined components:
 - Type hints for full IDE support
 - Context manager support (with statement)
 - Cross-platform library loading (Windows/Linux/macOS)
+- **NEW:** Instance method wrappers that accept 'this_handle' as first parameter
+- **NEW:** Automatic documentation comments indicating instance vs extension methods
+
+**Example for Extension Method (Python):**
+```python
+def align_center(self, container: Handle) -> Handle:
+    """
+    AlignmentExtensions.AlignCenter
+    """
+    result = self._lib.questpdf_fluent_alignmentextensions_aligncenter(
+        container.value if isinstance(container, Handle) else container
+    )
+    return Handle(self, result)
+```
+
+**Example for Instance Method (Python):**
+```python
+def align_center(self, this_handle: Handle) -> Handle:
+    """
+    TextBlockDescriptor.AlignCenter
+    Instance method - requires handle to the object.
+    """
+    result = self._lib.questpdf_fluent_textblockdescriptor_aligncenter(
+        this_handle.value if isinstance(this_handle, Handle) else this_handle
+    )
+    return Handle(self, result)
+```
 
 ### 4. PublicApiGenerator.cs
 **Purpose:** Main orchestrator that combines all components.
 
 **Responsibilities:**
 - Implements IIncrementalGenerator for Roslyn
-- Coordinates the 3-step pipeline:
-  1. Analyze API → collect methods
+- **UPDATED:** Coordinates the 3-step pipeline:
+  1. Analyze API → collect **ALL** interop methods (extension methods + Fluent API class methods)
   2. Generate C# code → add to compilation
-  3. Generate Python code → add as text file
+  3. Generate Python code → **strictly following all C# interop functionalities**
 - Registers source outputs with the compiler
 
 **Pipeline Flow:**
 ```
 Compilation
-PublicApiAnalyzer.CollectExtensionMethods()
+PublicApiAnalyzer.CollectAllInteropMethods()
+    ├─→ CollectExtensionMethods() (public static extension methods)
+    └─→ CollectFluentApiMethods() (public instance methods from descriptors, etc.)
-[List of IMethodSymbol]
+[List of IMethodSymbol - BOTH extension methods AND instance methods]
     ├─→ CSharpInteropGenerator.GenerateInteropCode()
     │       ↓
     │   GeneratedInterop.g.cs (added to compilation)
+    │   - Extension methods: static wrappers
+    │   - Instance methods: wrappers with 'this' parameter
     └─→ PythonBindingsGenerator.GeneratePythonBindings()
         GeneratedInterop.g.py.txt (available in obj folder)
+        - Extension methods: accept container/object handle
+        - Instance methods: accept 'this_handle' as first parameter
+        - Python bindings strictly mirror C# interop functionality
 ```
 
+## Complete Fluent API Coverage
+
+The interop generators now provide **complete coverage** of the QuestPDF Fluent API, including:
+
+### 1. Extension Methods (Original Coverage)
+All public static extension methods from classes like:
+- `AlignmentExtensions`, `PaddingExtensions`, `RowExtensions`, `ColumnExtensions`
+- `TextExtensions`, `ImageExtensions`, `TableExtensions`, `PageExtensions`
+- `DecorationExtensions`, `LayerExtensions`, `RotateExtensions`, `ScaleExtensions`
+- And many more...
+
+### 2. Fluent API Descriptor Classes (NEW)
+All public instance methods from descriptor classes including:
+
+**Text Descriptors:**
+- `TextSpanDescriptor` - Style(), FontSize(), FontColor(), etc.
+- `TextPageNumberDescriptor` - Format(), and all inherited TextSpanDescriptor methods
+- `TextBlockDescriptor` - AlignLeft(), AlignCenter(), Justify(), ClampLines(), etc.
+- `TextDescriptor` - Span(), Line(), EmptyLine(), CurrentPageNumber(), TotalPages(), etc.
+
+**Table Descriptors:**
+- `TableColumnsDefinitionDescriptor` - ConstantColumn(), RelativeColumn()
+- `TableCellDescriptor` - Cell()
+- `TableDescriptor` - ColumnsDefinition(), Header(), Footer(), Cell(), etc.
+
+**Layout Descriptors:**
+- `PageDescriptor` - Size(), Margin(), Content(), Header(), Footer(), etc.
+- `ColumnDescriptor` - Item(), Spacing()
+- `RowDescriptor` - AutoItem(), RelativeItem(), ConstantItem()
+- `DecorationDescriptor` - Before(), Content(), After()
+- `LayersDescriptor` - Layer(), PrimaryLayer()
+- `GridDescriptor` - Item(), Columns(), Spacing()
+
+**Image/SVG Descriptors:**
+- `ImageDescriptor` - FitArea(), FitWidth(), FitHeight(), etc.
+- `DynamicImageDescriptor` - Image configuration methods
+- `SvgImageDescriptor` - SVG-specific configuration
+
+**Other Descriptors:**
+- `InlinedDescriptor` - Item(), Spacing()
+- `MultiColumnDescriptor` - Columns(), Spacing()
+
+### 3. Configuration Classes (NEW)
+Public instance methods from configuration classes:
+- `DocumentOperation.LayerConfiguration` - FilePath, TargetPages, SourcePages, etc.
+- `DocumentOperation.DocumentAttachment` - Key, FilePath, AttachmentName, etc.
+- And other configuration classes
+
+### 4. Builder/Handler Classes (NEW)
+Any public classes with Builder, Handler, or Settings suffix that expose public instance methods.
+
 ## Type Support
 
 ### Supported Types

+ 54 - 0
Source/QuestPDF.PythonExtractor/questpdf_example.py

@@ -0,0 +1,54 @@
+"""
+Example usage of QuestPDF Python bindings
+
+This demonstrates how to use the auto-generated Python wrapper for QuestPDF.
+Make sure the QuestPDF native library is in the same directory or provide the path.
+"""
+
+from questpdf import QuestPDFLibrary, Handle
+
+
+def create_simple_document():
+    """Create a simple PDF document using QuestPDF"""
+    
+    # Initialize the library
+    # You can pass a custom path: QuestPDFLibrary('path/to/QuestPDF.dll')
+    pdf = QuestPDFLibrary()
+    
+    try:
+        # Example: Create a container and apply styling
+        # This is a conceptual example - actual method names will match your generated API
+        
+        # Create a document container
+        container = pdf.some_container_method()
+        
+        # Apply alignment (example based on AlignRight method mentioned)
+        with container as c:
+            aligned = pdf.align_right(c)
+            
+            # Apply padding
+            padded = pdf.padding(aligned, 10)
+            
+            # Add text or other content
+            # result = pdf.text(padded, "Hello from Python!")
+            
+            print("Document created successfully!")
+            
+    except Exception as e:
+        print(f"Error creating document: {e}")
+
+
+def main():
+    """Main entry point"""
+    print("QuestPDF Python Example")
+    print("=" * 50)
+    
+    try:
+        create_simple_document()
+    except Exception as e:
+        print(f"Fatal error: {e}")
+
+
+if __name__ == "__main__":
+    main()
+

+ 45 - 17
Source/QuestPDF/Interop.cs

@@ -68,16 +68,13 @@ public unsafe class Interop
     
     
     [UnmanagedCallersOnly(EntryPoint = "questpdf_document_create", CallConvs = new[] { typeof(CallConvCdecl) })]
-    public static nint Document_Create(delegate* unmanaged[Cdecl]<nint, void> pageHandler) // returns opaque handle
+    public static nint Document_Create(delegate* unmanaged[Cdecl]<nint, void> documentContainerHandler) // returns opaque handle
     {
         var thing = Document
-            .Create(document =>
+            .Create(documentContainer =>
             {
-                document.Page(page =>
-                {
-                    var pagePointer = BoxHandle(page);
-                    pageHandler(pagePointer);
-                });
+                var pagePointer = BoxHandle(documentContainer);
+                documentContainerHandler(pagePointer);
             });
         
         return BoxHandle(thing);
@@ -112,38 +109,69 @@ public unsafe class Interop
     
     
     
+    [UnmanagedCallersOnly(EntryPoint = "questpdf_document_container_add_page", CallConvs = new[] { typeof(CallConvCdecl) })]
+    public static void Document_ContainerAddPage(nint documentContainerPointer, delegate* unmanaged[Cdecl]<nint, void> descriptor) // returns opaque handle
+    {
+        var documentContainer = UnboxHandle<IDocumentContainer>(documentContainerPointer);
+        
+        documentContainer.Page(page =>
+        {
+            var pagePointer = BoxHandle(page);
+            descriptor(pagePointer);
+        });
+    }
+
+    [UnmanagedCallersOnly(EntryPoint = "questpdf_document_container_add_page_faster", CallConvs = new[] { typeof(CallConvCdecl) })]
+    public static IntPtr Document_ContainerAddPage_Faster(nint documentContainerPointer) // returns opaque handle
+    {
+        var documentContainer = UnboxHandle<IDocumentContainer>(documentContainerPointer);
+        
+        IntPtr pagePointer = default;
+        
+        documentContainer.Page(page =>
+        {
+            pagePointer = BoxHandle(page);
+        });
+
+        return pagePointer;
+    }
+    
     
     
     
     
 
     [UnmanagedCallersOnly(EntryPoint = "questpdf_page_set_margin", CallConvs = new[] { typeof(CallConvCdecl) })]
-    public static void Page_GeneratePdf(nint handle, int value)
+    public static void Page_SetMargins(nint handle, int value)
     {
         var thing = UnboxHandle<PageDescriptor>(handle);
         thing.Margin(value);
     }
 
     [UnmanagedCallersOnly(EntryPoint = "questpdf_page_set_content", CallConvs = new[] { typeof(CallConvCdecl) })]
-    public static void Page_GeneratePdf(nint handle, byte* textPtr)
+    public static void Page_AddContent(nint handle, byte* textPtr)
     {
         var thing = UnboxHandle<PageDescriptor>(handle);
         var textFromOutside = Marshal.PtrToStringUTF8((IntPtr)textPtr) ?? "";
         
-        thing.Content().Text(text =>
-        {
-            text.DefaultTextStyle(x => x.FontSize(20));
-
-            text.Span("Hello World... from ");
-            text.Span(textFromOutside).FontColor(Colors.Blue.Darken1);
-            text.Span("!");
-        });
+        thing.Content().Width(200).Height(100).Background(Colors.Red.Lighten3);
+            
+        //     .Text(text =>
+        // {
+        //     text.DefaultTextStyle(x => x.FontSize(20));
+        //
+        //     text.Span("Hello World... from ");
+        //     text.Span(textFromOutside).FontColor(Colors.Blue.Darken1);
+        //     text.Span("!");
+        // });
     }
     
     
     
     
     
+    
+    
 
     [UnmanagedCallersOnly(EntryPoint = "questpdf_free_bytes", CallConvs = new[] { typeof(CallConvCdecl) })]
     public static void FreeBytes(byte* ptr)