|
|
@@ -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
|