aiecs 1.0.8__py3-none-any.whl → 1.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of aiecs might be problematic. Click here for more details.

Files changed (45) hide show
  1. aiecs/__init__.py +1 -1
  2. aiecs/aiecs_client.py +159 -1
  3. aiecs/config/config.py +4 -0
  4. aiecs/domain/context/__init__.py +24 -0
  5. aiecs/main.py +20 -2
  6. aiecs/scripts/dependance_check/__init__.py +18 -0
  7. aiecs/scripts/{download_nlp_data.py → dependance_check/download_nlp_data.py} +50 -8
  8. aiecs/scripts/dependance_patch/__init__.py +8 -0
  9. aiecs/scripts/dependance_patch/fix_weasel/__init__.py +12 -0
  10. aiecs/scripts/tools_develop/README.md +340 -0
  11. aiecs/scripts/tools_develop/__init__.py +16 -0
  12. aiecs/scripts/tools_develop/check_type_annotations.py +263 -0
  13. aiecs/scripts/tools_develop/validate_tool_schemas.py +346 -0
  14. aiecs/tools/__init__.py +33 -14
  15. aiecs/tools/docs/__init__.py +103 -0
  16. aiecs/tools/docs/ai_document_orchestrator.py +543 -0
  17. aiecs/tools/docs/ai_document_writer_orchestrator.py +2199 -0
  18. aiecs/tools/docs/content_insertion_tool.py +1214 -0
  19. aiecs/tools/docs/document_creator_tool.py +1161 -0
  20. aiecs/tools/docs/document_layout_tool.py +1090 -0
  21. aiecs/tools/docs/document_parser_tool.py +904 -0
  22. aiecs/tools/docs/document_writer_tool.py +1583 -0
  23. aiecs/tools/langchain_adapter.py +102 -51
  24. aiecs/tools/schema_generator.py +265 -0
  25. aiecs/tools/task_tools/image_tool.py +1 -1
  26. aiecs/tools/task_tools/office_tool.py +9 -0
  27. aiecs/tools/task_tools/scraper_tool.py +1 -1
  28. {aiecs-1.0.8.dist-info → aiecs-1.1.0.dist-info}/METADATA +1 -1
  29. {aiecs-1.0.8.dist-info → aiecs-1.1.0.dist-info}/RECORD +44 -28
  30. aiecs-1.1.0.dist-info/entry_points.txt +9 -0
  31. aiecs-1.0.8.dist-info/entry_points.txt +0 -7
  32. /aiecs/scripts/{DEPENDENCY_SYSTEM_SUMMARY.md → dependance_check/DEPENDENCY_SYSTEM_SUMMARY.md} +0 -0
  33. /aiecs/scripts/{README_DEPENDENCY_CHECKER.md → dependance_check/README_DEPENDENCY_CHECKER.md} +0 -0
  34. /aiecs/scripts/{dependency_checker.py → dependance_check/dependency_checker.py} +0 -0
  35. /aiecs/scripts/{dependency_fixer.py → dependance_check/dependency_fixer.py} +0 -0
  36. /aiecs/scripts/{quick_dependency_check.py → dependance_check/quick_dependency_check.py} +0 -0
  37. /aiecs/scripts/{setup_nlp_data.sh → dependance_check/setup_nlp_data.sh} +0 -0
  38. /aiecs/scripts/{README_WEASEL_PATCH.md → dependance_patch/fix_weasel/README_WEASEL_PATCH.md} +0 -0
  39. /aiecs/scripts/{fix_weasel_validator.py → dependance_patch/fix_weasel/fix_weasel_validator.py} +0 -0
  40. /aiecs/scripts/{fix_weasel_validator.sh → dependance_patch/fix_weasel/fix_weasel_validator.sh} +0 -0
  41. /aiecs/scripts/{patch_weasel_library.sh → dependance_patch/fix_weasel/patch_weasel_library.sh} +0 -0
  42. /aiecs/scripts/{run_weasel_patch.sh → dependance_patch/fix_weasel/run_weasel_patch.sh} +0 -0
  43. {aiecs-1.0.8.dist-info → aiecs-1.1.0.dist-info}/WHEEL +0 -0
  44. {aiecs-1.0.8.dist-info → aiecs-1.1.0.dist-info}/licenses/LICENSE +0 -0
  45. {aiecs-1.0.8.dist-info → aiecs-1.1.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1090 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Document Layout Tool
4
+
5
+ This tool is responsible for document layout, page formatting, and
6
+ visual presentation of documents across different formats.
7
+
8
+ Key Features:
9
+ 1. Page layout management (margins, orientation, size)
10
+ 2. Multi-column layouts and text flow
11
+ 3. Headers, footers, and page numbering
12
+ 4. Section breaks and page breaks
13
+ 5. Typography and spacing control
14
+ 6. Format-specific layout optimization
15
+ """
16
+
17
+ import os
18
+ import json
19
+ import uuid
20
+ import tempfile
21
+ import logging
22
+ from datetime import datetime
23
+ from typing import Dict, Any, List, Optional, Union, Tuple
24
+ from enum import Enum
25
+ from pathlib import Path
26
+
27
+ from pydantic import BaseModel, Field, ValidationError
28
+ from pydantic_settings import BaseSettings
29
+
30
+ from aiecs.tools.base_tool import BaseTool
31
+ from aiecs.tools import register_tool
32
+
33
+
34
+ class PageSize(str, Enum):
35
+ """Standard page sizes"""
36
+ A4 = "a4"
37
+ A3 = "a3"
38
+ A5 = "a5"
39
+ LETTER = "letter"
40
+ LEGAL = "legal"
41
+ TABLOID = "tabloid"
42
+ CUSTOM = "custom"
43
+
44
+
45
+ class PageOrientation(str, Enum):
46
+ """Page orientations"""
47
+ PORTRAIT = "portrait"
48
+ LANDSCAPE = "landscape"
49
+
50
+
51
+ class LayoutType(str, Enum):
52
+ """Document layout types"""
53
+ SINGLE_COLUMN = "single_column"
54
+ TWO_COLUMN = "two_column"
55
+ THREE_COLUMN = "three_column"
56
+ MULTI_COLUMN = "multi_column"
57
+ MAGAZINE = "magazine"
58
+ NEWSPAPER = "newspaper"
59
+ ACADEMIC = "academic"
60
+ CUSTOM = "custom"
61
+
62
+
63
+ class AlignmentType(str, Enum):
64
+ """Text alignment types"""
65
+ LEFT = "left"
66
+ CENTER = "center"
67
+ RIGHT = "right"
68
+ JUSTIFY = "justify"
69
+
70
+
71
+ class BreakType(str, Enum):
72
+ """Break types"""
73
+ PAGE_BREAK = "page_break"
74
+ SECTION_BREAK = "section_break"
75
+ COLUMN_BREAK = "column_break"
76
+ LINE_BREAK = "line_break"
77
+
78
+
79
+ class HeaderFooterPosition(str, Enum):
80
+ """Header/footer positions"""
81
+ HEADER_LEFT = "header_left"
82
+ HEADER_CENTER = "header_center"
83
+ HEADER_RIGHT = "header_right"
84
+ FOOTER_LEFT = "footer_left"
85
+ FOOTER_CENTER = "footer_center"
86
+ FOOTER_RIGHT = "footer_right"
87
+
88
+
89
+ class DocumentLayoutSettings(BaseSettings):
90
+ """Configuration for DocumentLayoutTool"""
91
+ temp_dir: str = os.path.join(tempfile.gettempdir(), 'document_layouts')
92
+ default_page_size: PageSize = PageSize.A4
93
+ default_orientation: PageOrientation = PageOrientation.PORTRAIT
94
+ default_margins: Dict[str, float] = {"top": 2.5, "bottom": 2.5, "left": 2.5, "right": 2.5}
95
+ auto_adjust_layout: bool = True
96
+ preserve_formatting: bool = True
97
+
98
+ class Config:
99
+ env_prefix = "DOC_LAYOUT_"
100
+
101
+
102
+ class DocumentLayoutError(Exception):
103
+ """Base exception for Document Layout errors"""
104
+ pass
105
+
106
+
107
+ class LayoutConfigurationError(DocumentLayoutError):
108
+ """Raised when layout configuration fails"""
109
+ pass
110
+
111
+
112
+ class PageSetupError(DocumentLayoutError):
113
+ """Raised when page setup fails"""
114
+ pass
115
+
116
+
117
+ @register_tool("document_layout")
118
+ class DocumentLayoutTool(BaseTool):
119
+ """
120
+ Document Layout Tool for managing document presentation and formatting
121
+
122
+ This tool provides:
123
+ 1. Page setup and formatting
124
+ 2. Multi-column layouts
125
+ 3. Headers, footers, and page numbering
126
+ 4. Typography and spacing control
127
+ 5. Break management (page, section, column)
128
+ 6. Format-specific layout optimization
129
+
130
+ Integrates with:
131
+ - DocumentCreatorTool for initial document setup
132
+ - DocumentWriterTool for content placement
133
+ - ContentInsertionTool for complex content positioning
134
+ """
135
+
136
+ def __init__(self, config: Optional[Dict] = None):
137
+ """Initialize Document Layout Tool with settings"""
138
+ super().__init__(config)
139
+ self.settings = DocumentLayoutSettings()
140
+ if config:
141
+ try:
142
+ self.settings = self.settings.model_validate({**self.settings.model_dump(), **config})
143
+ except ValidationError as e:
144
+ raise ValueError(f"Invalid settings: {e}")
145
+
146
+ self.logger = logging.getLogger(__name__)
147
+
148
+ # Initialize directories
149
+ self._init_directories()
150
+
151
+ # Initialize layout presets
152
+ self._init_layout_presets()
153
+
154
+ # Track layout operations
155
+ self._layout_operations = []
156
+
157
+ def _init_directories(self):
158
+ """Initialize required directories"""
159
+ os.makedirs(self.settings.temp_dir, exist_ok=True)
160
+
161
+ def _init_layout_presets(self):
162
+ """Initialize built-in layout presets"""
163
+ self.layout_presets = {
164
+ "default": self._get_default_layout(),
165
+ "academic_paper": self._get_academic_paper_layout(),
166
+ "business_report": self._get_business_report_layout(),
167
+ "magazine": self._get_magazine_layout(),
168
+ "newspaper": self._get_newspaper_layout(),
169
+ "presentation": self._get_presentation_layout(),
170
+ "technical_doc": self._get_technical_doc_layout(),
171
+ "letter": self._get_letter_layout(),
172
+ "invoice": self._get_invoice_layout(),
173
+ "brochure": self._get_brochure_layout()
174
+ }
175
+
176
+ # Schema definitions
177
+ class SetPageLayoutSchema(BaseModel):
178
+ """Schema for set_page_layout operation"""
179
+ document_path: str = Field(description="Path to document")
180
+ page_size: PageSize = Field(description="Page size")
181
+ orientation: PageOrientation = Field(description="Page orientation")
182
+ margins: Dict[str, float] = Field(description="Page margins (top, bottom, left, right)")
183
+ layout_preset: Optional[str] = Field(default=None, description="Layout preset name")
184
+
185
+ class CreateMultiColumnSchema(BaseModel):
186
+ """Schema for create_multi_column_layout operation"""
187
+ document_path: str = Field(description="Path to document")
188
+ num_columns: int = Field(description="Number of columns")
189
+ column_gap: float = Field(default=1.0, description="Gap between columns (cm)")
190
+ column_widths: Optional[List[float]] = Field(default=None, description="Custom column widths")
191
+ balance_columns: bool = Field(default=True, description="Balance column heights")
192
+
193
+ class SetupHeadersFootersSchema(BaseModel):
194
+ """Schema for setup_headers_footers operation"""
195
+ document_path: str = Field(description="Path to document")
196
+ header_config: Optional[Dict[str, Any]] = Field(default=None, description="Header configuration")
197
+ footer_config: Optional[Dict[str, Any]] = Field(default=None, description="Footer configuration")
198
+ page_numbering: bool = Field(default=True, description="Include page numbering")
199
+ numbering_style: str = Field(default="numeric", description="Page numbering style")
200
+
201
+ class InsertBreakSchema(BaseModel):
202
+ """Schema for insert_break operation"""
203
+ document_path: str = Field(description="Path to document")
204
+ break_type: BreakType = Field(description="Type of break to insert")
205
+ position: Optional[Dict[str, Any]] = Field(default=None, description="Position to insert break")
206
+ break_options: Optional[Dict[str, Any]] = Field(default=None, description="Break-specific options")
207
+
208
+ class ConfigureTypographySchema(BaseModel):
209
+ """Schema for configure_typography operation"""
210
+ document_path: str = Field(description="Path to document")
211
+ font_config: Dict[str, Any] = Field(description="Font configuration")
212
+ spacing_config: Optional[Dict[str, Any]] = Field(default=None, description="Spacing configuration")
213
+ alignment: Optional[AlignmentType] = Field(default=None, description="Text alignment")
214
+
215
+ def set_page_layout(self,
216
+ document_path: str,
217
+ page_size: PageSize,
218
+ orientation: PageOrientation,
219
+ margins: Dict[str, float],
220
+ layout_preset: Optional[str] = None) -> Dict[str, Any]:
221
+ """
222
+ Set page layout configuration for document
223
+
224
+ Args:
225
+ document_path: Path to document
226
+ page_size: Page size (A4, Letter, etc.)
227
+ orientation: Page orientation (portrait/landscape)
228
+ margins: Page margins in cm (top, bottom, left, right)
229
+ layout_preset: Optional layout preset to apply
230
+
231
+ Returns:
232
+ Dict containing layout configuration results
233
+ """
234
+ try:
235
+ start_time = datetime.now()
236
+ operation_id = str(uuid.uuid4())
237
+
238
+ self.logger.info(f"Setting page layout {operation_id} for: {document_path}")
239
+
240
+ # Validate margins
241
+ required_margins = ["top", "bottom", "left", "right"]
242
+ for margin in required_margins:
243
+ if margin not in margins:
244
+ raise LayoutConfigurationError(f"Missing margin: {margin}")
245
+
246
+ # Apply layout preset if specified
247
+ if layout_preset:
248
+ preset_config = self._get_layout_preset(layout_preset)
249
+ if preset_config:
250
+ page_size = preset_config.get("page_size", page_size)
251
+ orientation = preset_config.get("orientation", orientation)
252
+ margins.update(preset_config.get("margins", {}))
253
+
254
+ # Create layout configuration
255
+ layout_config = {
256
+ "page_size": page_size,
257
+ "orientation": orientation,
258
+ "margins": margins,
259
+ "layout_preset": layout_preset,
260
+ "dimensions": self._calculate_page_dimensions(page_size, orientation, margins)
261
+ }
262
+
263
+ # Apply layout to document
264
+ self._apply_page_layout_to_document(document_path, layout_config)
265
+
266
+ # Track operation
267
+ operation_info = {
268
+ "operation_id": operation_id,
269
+ "operation_type": "set_page_layout",
270
+ "document_path": document_path,
271
+ "layout_config": layout_config,
272
+ "timestamp": start_time.isoformat(),
273
+ "duration": (datetime.now() - start_time).total_seconds()
274
+ }
275
+
276
+ self._layout_operations.append(operation_info)
277
+
278
+ self.logger.info(f"Page layout {operation_id} applied successfully")
279
+ return operation_info
280
+
281
+ except Exception as e:
282
+ raise PageSetupError(f"Failed to set page layout: {str(e)}")
283
+
284
+ def create_multi_column_layout(self,
285
+ document_path: str,
286
+ num_columns: int,
287
+ column_gap: float = 1.0,
288
+ column_widths: Optional[List[float]] = None,
289
+ balance_columns: bool = True) -> Dict[str, Any]:
290
+ """
291
+ Create multi-column layout for document
292
+
293
+ Args:
294
+ document_path: Path to document
295
+ num_columns: Number of columns
296
+ column_gap: Gap between columns in cm
297
+ column_widths: Custom column widths (if None, equal widths)
298
+ balance_columns: Whether to balance column heights
299
+
300
+ Returns:
301
+ Dict containing multi-column layout results
302
+ """
303
+ try:
304
+ start_time = datetime.now()
305
+ operation_id = str(uuid.uuid4())
306
+
307
+ self.logger.info(f"Creating {num_columns}-column layout {operation_id} for: {document_path}")
308
+
309
+ # Validate parameters
310
+ if num_columns < 1:
311
+ raise LayoutConfigurationError("Number of columns must be at least 1")
312
+ if column_widths and len(column_widths) != num_columns:
313
+ raise LayoutConfigurationError("Column widths count must match number of columns")
314
+
315
+ # Calculate column configuration
316
+ column_config = self._calculate_column_configuration(
317
+ num_columns, column_gap, column_widths, balance_columns
318
+ )
319
+
320
+ # Apply multi-column layout
321
+ self._apply_multi_column_layout(document_path, column_config)
322
+
323
+ operation_info = {
324
+ "operation_id": operation_id,
325
+ "operation_type": "create_multi_column_layout",
326
+ "document_path": document_path,
327
+ "column_config": column_config,
328
+ "timestamp": start_time.isoformat(),
329
+ "duration": (datetime.now() - start_time).total_seconds()
330
+ }
331
+
332
+ self._layout_operations.append(operation_info)
333
+
334
+ self.logger.info(f"Multi-column layout {operation_id} created successfully")
335
+ return operation_info
336
+
337
+ except Exception as e:
338
+ raise LayoutConfigurationError(f"Failed to create multi-column layout: {str(e)}")
339
+
340
+ def setup_headers_footers(self,
341
+ document_path: str,
342
+ header_config: Optional[Dict[str, Any]] = None,
343
+ footer_config: Optional[Dict[str, Any]] = None,
344
+ page_numbering: bool = True,
345
+ numbering_style: str = "numeric") -> Dict[str, Any]:
346
+ """
347
+ Setup headers and footers for document
348
+
349
+ Args:
350
+ document_path: Path to document
351
+ header_config: Header configuration
352
+ footer_config: Footer configuration
353
+ page_numbering: Include page numbering
354
+ numbering_style: Page numbering style (numeric, roman, alpha)
355
+
356
+ Returns:
357
+ Dict containing header/footer setup results
358
+ """
359
+ try:
360
+ start_time = datetime.now()
361
+ operation_id = str(uuid.uuid4())
362
+
363
+ self.logger.info(f"Setting up headers/footers {operation_id} for: {document_path}")
364
+
365
+ # Process header configuration
366
+ processed_header = self._process_header_footer_config(
367
+ header_config, "header", page_numbering, numbering_style
368
+ )
369
+
370
+ # Process footer configuration
371
+ processed_footer = self._process_header_footer_config(
372
+ footer_config, "footer", page_numbering, numbering_style
373
+ )
374
+
375
+ # Apply headers and footers
376
+ self._apply_headers_footers(document_path, processed_header, processed_footer)
377
+
378
+ operation_info = {
379
+ "operation_id": operation_id,
380
+ "operation_type": "setup_headers_footers",
381
+ "document_path": document_path,
382
+ "header_config": processed_header,
383
+ "footer_config": processed_footer,
384
+ "page_numbering": page_numbering,
385
+ "numbering_style": numbering_style,
386
+ "timestamp": start_time.isoformat(),
387
+ "duration": (datetime.now() - start_time).total_seconds()
388
+ }
389
+
390
+ self._layout_operations.append(operation_info)
391
+
392
+ self.logger.info(f"Headers/footers {operation_id} setup successfully")
393
+ return operation_info
394
+
395
+ except Exception as e:
396
+ raise LayoutConfigurationError(f"Failed to setup headers/footers: {str(e)}")
397
+
398
+ def insert_break(self,
399
+ document_path: str,
400
+ break_type: BreakType,
401
+ position: Optional[Dict[str, Any]] = None,
402
+ break_options: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
403
+ """
404
+ Insert page, section, or column break
405
+
406
+ Args:
407
+ document_path: Path to document
408
+ break_type: Type of break to insert
409
+ position: Position to insert break (line, offset, etc.)
410
+ break_options: Break-specific options
411
+
412
+ Returns:
413
+ Dict containing break insertion results
414
+ """
415
+ try:
416
+ start_time = datetime.now()
417
+ operation_id = str(uuid.uuid4())
418
+
419
+ self.logger.info(f"Inserting {break_type} break {operation_id} in: {document_path}")
420
+
421
+ # Determine break markup based on type and format
422
+ break_markup = self._generate_break_markup(break_type, break_options)
423
+
424
+ # Insert break at specified position
425
+ self._insert_break_at_position(document_path, break_markup, position)
426
+
427
+ operation_info = {
428
+ "operation_id": operation_id,
429
+ "operation_type": "insert_break",
430
+ "document_path": document_path,
431
+ "break_type": break_type,
432
+ "position": position,
433
+ "break_options": break_options,
434
+ "break_markup": break_markup,
435
+ "timestamp": start_time.isoformat(),
436
+ "duration": (datetime.now() - start_time).total_seconds()
437
+ }
438
+
439
+ self._layout_operations.append(operation_info)
440
+
441
+ self.logger.info(f"Break {operation_id} inserted successfully")
442
+ return operation_info
443
+
444
+ except Exception as e:
445
+ raise LayoutConfigurationError(f"Failed to insert break: {str(e)}")
446
+
447
+ def configure_typography(self,
448
+ document_path: str,
449
+ font_config: Dict[str, Any],
450
+ spacing_config: Optional[Dict[str, Any]] = None,
451
+ alignment: Optional[AlignmentType] = None) -> Dict[str, Any]:
452
+ """
453
+ Configure typography and text formatting
454
+
455
+ Args:
456
+ document_path: Path to document
457
+ font_config: Font configuration (family, size, weight, etc.)
458
+ spacing_config: Spacing configuration (line height, paragraph spacing)
459
+ alignment: Text alignment
460
+
461
+ Returns:
462
+ Dict containing typography configuration results
463
+ """
464
+ try:
465
+ start_time = datetime.now()
466
+ operation_id = str(uuid.uuid4())
467
+
468
+ self.logger.info(f"Configuring typography {operation_id} for: {document_path}")
469
+
470
+ # Process typography configuration
471
+ typography_config = self._process_typography_config(
472
+ font_config, spacing_config, alignment
473
+ )
474
+
475
+ # Apply typography settings
476
+ self._apply_typography_settings(document_path, typography_config)
477
+
478
+ operation_info = {
479
+ "operation_id": operation_id,
480
+ "operation_type": "configure_typography",
481
+ "document_path": document_path,
482
+ "typography_config": typography_config,
483
+ "timestamp": start_time.isoformat(),
484
+ "duration": (datetime.now() - start_time).total_seconds()
485
+ }
486
+
487
+ self._layout_operations.append(operation_info)
488
+
489
+ self.logger.info(f"Typography {operation_id} configured successfully")
490
+ return operation_info
491
+
492
+ except Exception as e:
493
+ raise LayoutConfigurationError(f"Failed to configure typography: {str(e)}")
494
+
495
+ def optimize_layout_for_content(self,
496
+ document_path: str,
497
+ content_analysis: Dict[str, Any],
498
+ optimization_goals: List[str]) -> Dict[str, Any]:
499
+ """
500
+ Optimize document layout based on content analysis
501
+
502
+ Args:
503
+ document_path: Path to document
504
+ content_analysis: Analysis of document content
505
+ optimization_goals: List of optimization goals
506
+
507
+ Returns:
508
+ Dict containing layout optimization results
509
+ """
510
+ try:
511
+ start_time = datetime.now()
512
+ operation_id = str(uuid.uuid4())
513
+
514
+ self.logger.info(f"Optimizing layout {operation_id} for: {document_path}")
515
+
516
+ # Analyze current layout
517
+ current_layout = self._analyze_current_layout(document_path)
518
+
519
+ # Generate optimization recommendations
520
+ optimization_plan = self._generate_optimization_plan(
521
+ current_layout, content_analysis, optimization_goals
522
+ )
523
+
524
+ # Apply optimizations
525
+ optimization_results = self._apply_layout_optimizations(
526
+ document_path, optimization_plan
527
+ )
528
+
529
+ operation_info = {
530
+ "operation_id": operation_id,
531
+ "operation_type": "optimize_layout_for_content",
532
+ "document_path": document_path,
533
+ "content_analysis": content_analysis,
534
+ "optimization_goals": optimization_goals,
535
+ "optimization_plan": optimization_plan,
536
+ "optimization_results": optimization_results,
537
+ "timestamp": start_time.isoformat(),
538
+ "duration": (datetime.now() - start_time).total_seconds()
539
+ }
540
+
541
+ self._layout_operations.append(operation_info)
542
+
543
+ self.logger.info(f"Layout optimization {operation_id} completed successfully")
544
+ return operation_info
545
+
546
+ except Exception as e:
547
+ raise LayoutConfigurationError(f"Failed to optimize layout: {str(e)}")
548
+
549
+ def get_layout_presets(self) -> Dict[str, Any]:
550
+ """
551
+ Get available layout presets
552
+
553
+ Returns:
554
+ Dict containing available layout presets
555
+ """
556
+ return {
557
+ "presets": list(self.layout_presets.keys()),
558
+ "preset_details": {name: preset.get("description", "")
559
+ for name, preset in self.layout_presets.items()}
560
+ }
561
+
562
+ def get_layout_operations(self) -> List[Dict[str, Any]]:
563
+ """
564
+ Get list of layout operations performed
565
+
566
+ Returns:
567
+ List of layout operation information
568
+ """
569
+ return self._layout_operations.copy()
570
+
571
+ # Layout preset definitions
572
+ def _get_default_layout(self) -> Dict[str, Any]:
573
+ """Get default layout configuration"""
574
+ return {
575
+ "description": "Standard single-column layout",
576
+ "page_size": PageSize.A4,
577
+ "orientation": PageOrientation.PORTRAIT,
578
+ "margins": {"top": 2.5, "bottom": 2.5, "left": 2.5, "right": 2.5},
579
+ "columns": 1,
580
+ "font": {"family": "Arial", "size": 12},
581
+ "spacing": {"line_height": 1.5, "paragraph_spacing": 6}
582
+ }
583
+
584
+ def _get_academic_paper_layout(self) -> Dict[str, Any]:
585
+ """Get academic paper layout configuration"""
586
+ return {
587
+ "description": "Academic paper with double spacing",
588
+ "page_size": PageSize.A4,
589
+ "orientation": PageOrientation.PORTRAIT,
590
+ "margins": {"top": 2.5, "bottom": 2.5, "left": 3.0, "right": 2.5},
591
+ "columns": 1,
592
+ "font": {"family": "Times New Roman", "size": 12},
593
+ "spacing": {"line_height": 2.0, "paragraph_spacing": 0},
594
+ "headers_footers": {
595
+ "header_right": "{author_name}",
596
+ "footer_center": "{page_number}"
597
+ }
598
+ }
599
+
600
+ def _get_business_report_layout(self) -> Dict[str, Any]:
601
+ """Get business report layout configuration"""
602
+ return {
603
+ "description": "Professional business report layout",
604
+ "page_size": PageSize.A4,
605
+ "orientation": PageOrientation.PORTRAIT,
606
+ "margins": {"top": 2.0, "bottom": 2.0, "left": 2.5, "right": 2.5},
607
+ "columns": 1,
608
+ "font": {"family": "Calibri", "size": 11},
609
+ "spacing": {"line_height": 1.15, "paragraph_spacing": 6},
610
+ "headers_footers": {
611
+ "header_left": "{document_title}",
612
+ "header_right": "{date}",
613
+ "footer_center": "Page {page_number} of {total_pages}",
614
+ "footer_right": "{company_name}"
615
+ }
616
+ }
617
+
618
+ def _get_magazine_layout(self) -> Dict[str, Any]:
619
+ """Get magazine layout configuration"""
620
+ return {
621
+ "description": "Multi-column magazine layout",
622
+ "page_size": PageSize.A4,
623
+ "orientation": PageOrientation.PORTRAIT,
624
+ "margins": {"top": 1.5, "bottom": 1.5, "left": 1.5, "right": 1.5},
625
+ "columns": 2,
626
+ "column_gap": 0.8,
627
+ "font": {"family": "Georgia", "size": 10},
628
+ "spacing": {"line_height": 1.3, "paragraph_spacing": 4}
629
+ }
630
+
631
+ def _get_newspaper_layout(self) -> Dict[str, Any]:
632
+ """Get newspaper layout configuration"""
633
+ return {
634
+ "description": "Multi-column newspaper layout",
635
+ "page_size": PageSize.TABLOID,
636
+ "orientation": PageOrientation.PORTRAIT,
637
+ "margins": {"top": 1.0, "bottom": 1.0, "left": 1.0, "right": 1.0},
638
+ "columns": 4,
639
+ "column_gap": 0.5,
640
+ "font": {"family": "Arial", "size": 9},
641
+ "spacing": {"line_height": 1.2, "paragraph_spacing": 3}
642
+ }
643
+
644
+ def _get_presentation_layout(self) -> Dict[str, Any]:
645
+ """Get presentation layout configuration"""
646
+ return {
647
+ "description": "Landscape presentation layout",
648
+ "page_size": PageSize.A4,
649
+ "orientation": PageOrientation.LANDSCAPE,
650
+ "margins": {"top": 2.0, "bottom": 2.0, "left": 2.0, "right": 2.0},
651
+ "columns": 1,
652
+ "font": {"family": "Helvetica", "size": 14},
653
+ "spacing": {"line_height": 1.4, "paragraph_spacing": 12}
654
+ }
655
+
656
+ def _get_technical_doc_layout(self) -> Dict[str, Any]:
657
+ """Get technical documentation layout configuration"""
658
+ return {
659
+ "description": "Technical documentation with wide margins for notes",
660
+ "page_size": PageSize.A4,
661
+ "orientation": PageOrientation.PORTRAIT,
662
+ "margins": {"top": 2.0, "bottom": 2.0, "left": 3.5, "right": 2.0},
663
+ "columns": 1,
664
+ "font": {"family": "Consolas", "size": 10},
665
+ "spacing": {"line_height": 1.4, "paragraph_spacing": 8}
666
+ }
667
+
668
+ def _get_letter_layout(self) -> Dict[str, Any]:
669
+ """Get letter layout configuration"""
670
+ return {
671
+ "description": "Standard business letter layout",
672
+ "page_size": PageSize.LETTER,
673
+ "orientation": PageOrientation.PORTRAIT,
674
+ "margins": {"top": 2.5, "bottom": 2.5, "left": 2.5, "right": 2.5},
675
+ "columns": 1,
676
+ "font": {"family": "Times New Roman", "size": 12},
677
+ "spacing": {"line_height": 1.0, "paragraph_spacing": 12}
678
+ }
679
+
680
+ def _get_invoice_layout(self) -> Dict[str, Any]:
681
+ """Get invoice layout configuration"""
682
+ return {
683
+ "description": "Invoice and billing document layout",
684
+ "page_size": PageSize.A4,
685
+ "orientation": PageOrientation.PORTRAIT,
686
+ "margins": {"top": 1.5, "bottom": 1.5, "left": 2.0, "right": 2.0},
687
+ "columns": 1,
688
+ "font": {"family": "Arial", "size": 10},
689
+ "spacing": {"line_height": 1.2, "paragraph_spacing": 4}
690
+ }
691
+
692
+ def _get_brochure_layout(self) -> Dict[str, Any]:
693
+ """Get brochure layout configuration"""
694
+ return {
695
+ "description": "Tri-fold brochure layout",
696
+ "page_size": PageSize.A4,
697
+ "orientation": PageOrientation.LANDSCAPE,
698
+ "margins": {"top": 1.0, "bottom": 1.0, "left": 1.0, "right": 1.0},
699
+ "columns": 3,
700
+ "column_gap": 0.5,
701
+ "font": {"family": "Verdana", "size": 9},
702
+ "spacing": {"line_height": 1.3, "paragraph_spacing": 6}
703
+ }
704
+
705
+ # Helper methods
706
+ def _get_layout_preset(self, preset_name: str) -> Optional[Dict[str, Any]]:
707
+ """Get layout preset by name"""
708
+ return self.layout_presets.get(preset_name)
709
+
710
+ def _calculate_page_dimensions(self, page_size: PageSize, orientation: PageOrientation,
711
+ margins: Dict[str, float]) -> Dict[str, float]:
712
+ """Calculate page dimensions including margins"""
713
+ # Standard page sizes in cm
714
+ page_sizes = {
715
+ PageSize.A4: (21.0, 29.7),
716
+ PageSize.A3: (29.7, 42.0),
717
+ PageSize.A5: (14.8, 21.0),
718
+ PageSize.LETTER: (21.59, 27.94),
719
+ PageSize.LEGAL: (21.59, 35.56),
720
+ PageSize.TABLOID: (27.94, 43.18)
721
+ }
722
+
723
+ width, height = page_sizes.get(page_size, (21.0, 29.7))
724
+
725
+ if orientation == PageOrientation.LANDSCAPE:
726
+ width, height = height, width
727
+
728
+ content_width = width - margins["left"] - margins["right"]
729
+ content_height = height - margins["top"] - margins["bottom"]
730
+
731
+ return {
732
+ "page_width": width,
733
+ "page_height": height,
734
+ "content_width": content_width,
735
+ "content_height": content_height,
736
+ "margins": margins
737
+ }
738
+
739
+ def _calculate_column_configuration(self, num_columns: int, column_gap: float,
740
+ column_widths: Optional[List[float]],
741
+ balance_columns: bool) -> Dict[str, Any]:
742
+ """Calculate column configuration"""
743
+ config = {
744
+ "num_columns": num_columns,
745
+ "column_gap": column_gap,
746
+ "balance_columns": balance_columns
747
+ }
748
+
749
+ if column_widths:
750
+ config["column_widths"] = column_widths
751
+ config["custom_widths"] = True
752
+ else:
753
+ # Equal column widths
754
+ config["custom_widths"] = False
755
+
756
+ return config
757
+
758
+ def _apply_page_layout_to_document(self, document_path: str, layout_config: Dict[str, Any]):
759
+ """Apply page layout configuration to document"""
760
+ # Detect document format
761
+ file_format = self._detect_document_format(document_path)
762
+
763
+ # Generate layout markup based on format
764
+ if file_format == "markdown":
765
+ layout_markup = self._generate_markdown_layout_markup(layout_config)
766
+ elif file_format == "html":
767
+ layout_markup = self._generate_html_layout_markup(layout_config)
768
+ elif file_format == "latex":
769
+ layout_markup = self._generate_latex_layout_markup(layout_config)
770
+ else:
771
+ layout_markup = self._generate_generic_layout_markup(layout_config)
772
+
773
+ # Insert layout markup into document
774
+ self._insert_layout_markup(document_path, layout_markup, "page_layout")
775
+
776
+ def _apply_multi_column_layout(self, document_path: str, column_config: Dict[str, Any]):
777
+ """Apply multi-column layout to document"""
778
+ file_format = self._detect_document_format(document_path)
779
+
780
+ if file_format == "html":
781
+ column_markup = self._generate_html_column_markup(column_config)
782
+ elif file_format == "latex":
783
+ column_markup = self._generate_latex_column_markup(column_config)
784
+ else:
785
+ column_markup = self._generate_generic_column_markup(column_config)
786
+
787
+ self._insert_layout_markup(document_path, column_markup, "multi_column")
788
+
789
+ def _apply_headers_footers(self, document_path: str, header_config: Dict[str, Any],
790
+ footer_config: Dict[str, Any]):
791
+ """Apply headers and footers to document"""
792
+ file_format = self._detect_document_format(document_path)
793
+
794
+ header_markup = self._generate_header_footer_markup(header_config, "header", file_format)
795
+ footer_markup = self._generate_header_footer_markup(footer_config, "footer", file_format)
796
+
797
+ self._insert_layout_markup(document_path, header_markup, "headers")
798
+ self._insert_layout_markup(document_path, footer_markup, "footers")
799
+
800
+ def _process_header_footer_config(self, config: Optional[Dict[str, Any]],
801
+ hf_type: str, page_numbering: bool,
802
+ numbering_style: str) -> Dict[str, Any]:
803
+ """Process header or footer configuration"""
804
+ processed = config.copy() if config else {}
805
+
806
+ # Add page numbering if requested
807
+ if page_numbering:
808
+ numbering_text = self._generate_page_numbering_text(numbering_style)
809
+ if hf_type == "footer" and "center" not in processed:
810
+ processed["center"] = numbering_text
811
+ elif hf_type == "header" and "right" not in processed:
812
+ processed["right"] = numbering_text
813
+
814
+ return processed
815
+
816
+ def _generate_page_numbering_text(self, style: str) -> str:
817
+ """Generate page numbering text based on style"""
818
+ if style == "roman":
819
+ return "{page_roman}"
820
+ elif style == "alpha":
821
+ return "{page_alpha}"
822
+ elif style == "with_total":
823
+ return "Page {page} of {total_pages}"
824
+ else: # numeric
825
+ return "{page}"
826
+
827
+ def _generate_break_markup(self, break_type: BreakType, options: Optional[Dict[str, Any]]) -> str:
828
+ """Generate break markup based on type"""
829
+ if break_type == BreakType.PAGE_BREAK:
830
+ return "\n<!-- PAGE BREAK -->\n\\newpage\n"
831
+ elif break_type == BreakType.SECTION_BREAK:
832
+ return "\n<!-- SECTION BREAK -->\n\\clearpage\n"
833
+ elif break_type == BreakType.COLUMN_BREAK:
834
+ return "\n<!-- COLUMN BREAK -->\n\\columnbreak\n"
835
+ elif break_type == BreakType.LINE_BREAK:
836
+ return "\n<!-- LINE BREAK -->\n\\linebreak\n"
837
+ else:
838
+ return "\n"
839
+
840
+ def _insert_break_at_position(self, document_path: str, break_markup: str,
841
+ position: Optional[Dict[str, Any]]):
842
+ """Insert break markup at specified position"""
843
+ try:
844
+ with open(document_path, 'r', encoding='utf-8') as f:
845
+ content = f.read()
846
+
847
+ if position:
848
+ if 'line' in position:
849
+ lines = content.split('\n')
850
+ line_num = position['line']
851
+ if 0 <= line_num <= len(lines):
852
+ lines.insert(line_num, break_markup.strip())
853
+ content = '\n'.join(lines)
854
+ elif 'offset' in position:
855
+ offset = position['offset']
856
+ content = content[:offset] + break_markup + content[offset:]
857
+ else:
858
+ # Append at end
859
+ content += break_markup
860
+
861
+ with open(document_path, 'w', encoding='utf-8') as f:
862
+ f.write(content)
863
+
864
+ except Exception as e:
865
+ raise LayoutConfigurationError(f"Failed to insert break: {str(e)}")
866
+
867
+ def _process_typography_config(self, font_config: Dict[str, Any],
868
+ spacing_config: Optional[Dict[str, Any]],
869
+ alignment: Optional[AlignmentType]) -> Dict[str, Any]:
870
+ """Process typography configuration"""
871
+ config = {
872
+ "font": font_config,
873
+ "spacing": spacing_config or {},
874
+ "alignment": alignment
875
+ }
876
+
877
+ # Validate font configuration
878
+ required_font_keys = ["family", "size"]
879
+ for key in required_font_keys:
880
+ if key not in font_config:
881
+ raise LayoutConfigurationError(f"Missing font configuration: {key}")
882
+
883
+ return config
884
+
885
+ def _apply_typography_settings(self, document_path: str, typography_config: Dict[str, Any]):
886
+ """Apply typography settings to document"""
887
+ file_format = self._detect_document_format(document_path)
888
+
889
+ if file_format == "html":
890
+ typography_markup = self._generate_html_typography_markup(typography_config)
891
+ elif file_format == "latex":
892
+ typography_markup = self._generate_latex_typography_markup(typography_config)
893
+ else:
894
+ typography_markup = self._generate_generic_typography_markup(typography_config)
895
+
896
+ self._insert_layout_markup(document_path, typography_markup, "typography")
897
+
898
+ def _analyze_current_layout(self, document_path: str) -> Dict[str, Any]:
899
+ """Analyze current document layout"""
900
+ try:
901
+ with open(document_path, 'r', encoding='utf-8') as f:
902
+ content = f.read()
903
+
904
+ return {
905
+ "content_length": len(content),
906
+ "line_count": len(content.split('\n')),
907
+ "word_count": len(content.split()),
908
+ "has_headers": "header" in content.lower(),
909
+ "has_columns": "column" in content.lower(),
910
+ "file_format": self._detect_document_format(document_path)
911
+ }
912
+ except Exception:
913
+ return {"error": "Failed to analyze layout"}
914
+
915
+ def _generate_optimization_plan(self, current_layout: Dict[str, Any],
916
+ content_analysis: Dict[str, Any],
917
+ optimization_goals: List[str]) -> Dict[str, Any]:
918
+ """Generate layout optimization plan"""
919
+ plan = {
920
+ "optimizations": [],
921
+ "goals": optimization_goals,
922
+ "current_layout": current_layout,
923
+ "content_analysis": content_analysis
924
+ }
925
+
926
+ # Add optimizations based on goals
927
+ for goal in optimization_goals:
928
+ if goal == "readability":
929
+ plan["optimizations"].append({
930
+ "type": "typography",
931
+ "action": "improve_readability",
932
+ "details": "Increase line height and adjust font size"
933
+ })
934
+ elif goal == "space_efficiency":
935
+ plan["optimizations"].append({
936
+ "type": "layout",
937
+ "action": "optimize_spacing",
938
+ "details": "Reduce margins and adjust paragraph spacing"
939
+ })
940
+ elif goal == "professional":
941
+ plan["optimizations"].append({
942
+ "type": "styling",
943
+ "action": "apply_professional_style",
944
+ "details": "Use professional fonts and consistent formatting"
945
+ })
946
+
947
+ return plan
948
+
949
+ def _apply_layout_optimizations(self, document_path: str, optimization_plan: Dict[str, Any]) -> Dict[str, Any]:
950
+ """Apply layout optimizations based on plan"""
951
+ results = {
952
+ "optimizations_applied": [],
953
+ "success_count": 0,
954
+ "error_count": 0
955
+ }
956
+
957
+ for optimization in optimization_plan.get("optimizations", []):
958
+ try:
959
+ # Apply optimization based on type
960
+ if optimization["type"] == "typography":
961
+ self._apply_typography_optimization(document_path, optimization)
962
+ elif optimization["type"] == "layout":
963
+ self._apply_layout_optimization(document_path, optimization)
964
+ elif optimization["type"] == "styling":
965
+ self._apply_styling_optimization(document_path, optimization)
966
+
967
+ results["optimizations_applied"].append(optimization)
968
+ results["success_count"] += 1
969
+
970
+ except Exception as e:
971
+ results["error_count"] += 1
972
+ self.logger.warning(f"Failed to apply optimization {optimization['type']}: {e}")
973
+
974
+ return results
975
+
976
+ def _apply_typography_optimization(self, document_path: str, optimization: Dict[str, Any]):
977
+ """Apply typography optimization"""
978
+ # Simplified implementation
979
+ pass
980
+
981
+ def _apply_layout_optimization(self, document_path: str, optimization: Dict[str, Any]):
982
+ """Apply layout optimization"""
983
+ # Simplified implementation
984
+ pass
985
+
986
+ def _apply_styling_optimization(self, document_path: str, optimization: Dict[str, Any]):
987
+ """Apply styling optimization"""
988
+ # Simplified implementation
989
+ pass
990
+
991
+ def _detect_document_format(self, document_path: str) -> str:
992
+ """Detect document format from file extension"""
993
+ ext = os.path.splitext(document_path)[1].lower()
994
+ format_map = {
995
+ '.md': 'markdown',
996
+ '.markdown': 'markdown',
997
+ '.html': 'html',
998
+ '.htm': 'html',
999
+ '.tex': 'latex',
1000
+ '.latex': 'latex',
1001
+ '.txt': 'text'
1002
+ }
1003
+ return format_map.get(ext, 'text')
1004
+
1005
+ def _generate_markdown_layout_markup(self, layout_config: Dict[str, Any]) -> str:
1006
+ """Generate Markdown layout markup"""
1007
+ return f"<!-- Layout: {layout_config['page_size']} {layout_config['orientation']} -->\n"
1008
+
1009
+ def _generate_html_layout_markup(self, layout_config: Dict[str, Any]) -> str:
1010
+ """Generate HTML layout markup"""
1011
+ margins = layout_config['margins']
1012
+ return f"""<style>
1013
+ @page {{
1014
+ size: {layout_config['page_size']};
1015
+ margin: {margins['top']}cm {margins['right']}cm {margins['bottom']}cm {margins['left']}cm;
1016
+ }}
1017
+ </style>"""
1018
+
1019
+ def _generate_latex_layout_markup(self, layout_config: Dict[str, Any]) -> str:
1020
+ """Generate LaTeX layout markup"""
1021
+ margins = layout_config['margins']
1022
+ return f"""\\usepackage[top={margins['top']}cm,bottom={margins['bottom']}cm,left={margins['left']}cm,right={margins['right']}cm]{{geometry}}
1023
+ \\usepackage[{layout_config['orientation']}]{{geometry}}"""
1024
+
1025
+ def _generate_generic_layout_markup(self, layout_config: Dict[str, Any]) -> str:
1026
+ """Generate generic layout markup"""
1027
+ return f"# Layout Configuration\nPage: {layout_config['page_size']} {layout_config['orientation']}\n"
1028
+
1029
+ def _generate_html_column_markup(self, column_config: Dict[str, Any]) -> str:
1030
+ """Generate HTML column markup"""
1031
+ num_cols = column_config['num_columns']
1032
+ gap = column_config['column_gap']
1033
+ return f"""<style>
1034
+ .multi-column {{
1035
+ column-count: {num_cols};
1036
+ column-gap: {gap}cm;
1037
+ }}
1038
+ </style>
1039
+ <div class="multi-column">"""
1040
+
1041
+ def _generate_latex_column_markup(self, column_config: Dict[str, Any]) -> str:
1042
+ """Generate LaTeX column markup"""
1043
+ return f"\\begin{{multicols}}{{{column_config['num_columns']}}}"
1044
+
1045
+ def _generate_generic_column_markup(self, column_config: Dict[str, Any]) -> str:
1046
+ """Generate generic column markup"""
1047
+ return f"<!-- {column_config['num_columns']} columns -->\n"
1048
+
1049
+ def _generate_header_footer_markup(self, config: Dict[str, Any], hf_type: str, file_format: str) -> str:
1050
+ """Generate header/footer markup"""
1051
+ if file_format == "html":
1052
+ return f"<!-- {hf_type.upper()}: {config} -->\n"
1053
+ elif file_format == "latex":
1054
+ return f"% {hf_type.upper()}: {config}\n"
1055
+ else:
1056
+ return f"# {hf_type.upper()}: {config}\n"
1057
+
1058
+ def _generate_html_typography_markup(self, typography_config: Dict[str, Any]) -> str:
1059
+ """Generate HTML typography markup"""
1060
+ font = typography_config['font']
1061
+ return f"""<style>
1062
+ body {{
1063
+ font-family: '{font['family']}';
1064
+ font-size: {font['size']}pt;
1065
+ }}
1066
+ </style>"""
1067
+
1068
+ def _generate_latex_typography_markup(self, typography_config: Dict[str, Any]) -> str:
1069
+ """Generate LaTeX typography markup"""
1070
+ font = typography_config['font']
1071
+ return f"\\usepackage{{fontspec}}\n\\setmainfont{{{font['family']}}}\n"
1072
+
1073
+ def _generate_generic_typography_markup(self, typography_config: Dict[str, Any]) -> str:
1074
+ """Generate generic typography markup"""
1075
+ return f"# Typography: {typography_config['font']}\n"
1076
+
1077
+ def _insert_layout_markup(self, document_path: str, markup: str, markup_type: str):
1078
+ """Insert layout markup into document"""
1079
+ try:
1080
+ with open(document_path, 'r', encoding='utf-8') as f:
1081
+ content = f.read()
1082
+
1083
+ # Insert at the beginning of document
1084
+ content = markup + "\n" + content
1085
+
1086
+ with open(document_path, 'w', encoding='utf-8') as f:
1087
+ f.write(content)
1088
+
1089
+ except Exception as e:
1090
+ raise LayoutConfigurationError(f"Failed to insert {markup_type} markup: {str(e)}")