aspose-cells-foss 25.12.1__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.
- aspose/__init__.py +14 -0
- aspose/cells/__init__.py +31 -0
- aspose/cells/cell.py +350 -0
- aspose/cells/constants.py +44 -0
- aspose/cells/converters/__init__.py +13 -0
- aspose/cells/converters/csv_converter.py +55 -0
- aspose/cells/converters/json_converter.py +46 -0
- aspose/cells/converters/markdown_converter.py +453 -0
- aspose/cells/drawing/__init__.py +17 -0
- aspose/cells/drawing/anchor.py +172 -0
- aspose/cells/drawing/collection.py +233 -0
- aspose/cells/drawing/image.py +338 -0
- aspose/cells/formats.py +80 -0
- aspose/cells/formula/__init__.py +10 -0
- aspose/cells/formula/evaluator.py +360 -0
- aspose/cells/formula/functions.py +433 -0
- aspose/cells/formula/tokenizer.py +340 -0
- aspose/cells/io/__init__.py +27 -0
- aspose/cells/io/csv/__init__.py +8 -0
- aspose/cells/io/csv/reader.py +88 -0
- aspose/cells/io/csv/writer.py +98 -0
- aspose/cells/io/factory.py +138 -0
- aspose/cells/io/interfaces.py +48 -0
- aspose/cells/io/json/__init__.py +8 -0
- aspose/cells/io/json/reader.py +126 -0
- aspose/cells/io/json/writer.py +119 -0
- aspose/cells/io/md/__init__.py +8 -0
- aspose/cells/io/md/reader.py +161 -0
- aspose/cells/io/md/writer.py +334 -0
- aspose/cells/io/models.py +64 -0
- aspose/cells/io/xlsx/__init__.py +9 -0
- aspose/cells/io/xlsx/constants.py +312 -0
- aspose/cells/io/xlsx/image_writer.py +311 -0
- aspose/cells/io/xlsx/reader.py +284 -0
- aspose/cells/io/xlsx/writer.py +931 -0
- aspose/cells/plugins/__init__.py +6 -0
- aspose/cells/plugins/docling_backend/__init__.py +7 -0
- aspose/cells/plugins/docling_backend/backend.py +535 -0
- aspose/cells/plugins/markitdown_plugin/__init__.py +15 -0
- aspose/cells/plugins/markitdown_plugin/plugin.py +128 -0
- aspose/cells/range.py +210 -0
- aspose/cells/style.py +287 -0
- aspose/cells/utils/__init__.py +54 -0
- aspose/cells/utils/coordinates.py +68 -0
- aspose/cells/utils/exceptions.py +43 -0
- aspose/cells/utils/validation.py +102 -0
- aspose/cells/workbook.py +352 -0
- aspose/cells/worksheet.py +670 -0
- aspose_cells_foss-25.12.1.dist-info/METADATA +189 -0
- aspose_cells_foss-25.12.1.dist-info/RECORD +53 -0
- aspose_cells_foss-25.12.1.dist-info/WHEEL +5 -0
- aspose_cells_foss-25.12.1.dist-info/entry_points.txt +2 -0
- aspose_cells_foss-25.12.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Image collection management for worksheets.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import List, Union, Optional, Iterator
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
import io
|
|
8
|
+
|
|
9
|
+
from .image import Image, ImageFormat
|
|
10
|
+
from .anchor import Anchor
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ImageCollection:
|
|
14
|
+
"""Collection manager for worksheet images with multiple access patterns."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, worksheet: 'Worksheet'):
|
|
17
|
+
self._worksheet = worksheet
|
|
18
|
+
self._images: List[Image] = []
|
|
19
|
+
|
|
20
|
+
def add(self, source: Union[str, Path, bytes, io.BytesIO, Image],
|
|
21
|
+
cell_ref: str = "A1",
|
|
22
|
+
width: Optional[int] = None,
|
|
23
|
+
height: Optional[int] = None,
|
|
24
|
+
name: Optional[str] = None) -> Image:
|
|
25
|
+
"""
|
|
26
|
+
Add image to worksheet at specified cell.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
source: Image source (file path, bytes, Image object)
|
|
30
|
+
cell_ref: Cell reference for positioning (default: "A1")
|
|
31
|
+
width: Optional width in pixels
|
|
32
|
+
height: Optional height in pixels
|
|
33
|
+
name: Optional image name
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Image: Added image object
|
|
37
|
+
"""
|
|
38
|
+
if isinstance(source, Image):
|
|
39
|
+
image = source.copy()
|
|
40
|
+
else:
|
|
41
|
+
image = Image(source)
|
|
42
|
+
|
|
43
|
+
# Set positioning
|
|
44
|
+
image.position_at(cell_ref)
|
|
45
|
+
|
|
46
|
+
# Resize if specified
|
|
47
|
+
if width is not None or height is not None:
|
|
48
|
+
image.resize(width, height)
|
|
49
|
+
|
|
50
|
+
# Set name
|
|
51
|
+
if name is not None:
|
|
52
|
+
image.name = name
|
|
53
|
+
elif image.name is None:
|
|
54
|
+
image.name = f"Image{len(self._images) + 1}"
|
|
55
|
+
|
|
56
|
+
# Ensure unique name
|
|
57
|
+
original_name = image.name
|
|
58
|
+
counter = 1
|
|
59
|
+
while any(img.name == image.name for img in self._images):
|
|
60
|
+
image.name = f"{original_name}_{counter}"
|
|
61
|
+
counter += 1
|
|
62
|
+
|
|
63
|
+
self._images.append(image)
|
|
64
|
+
return image
|
|
65
|
+
|
|
66
|
+
def extract(self, target: Union[str, int]) -> bytes:
|
|
67
|
+
"""
|
|
68
|
+
Extract image data as bytes.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
target: Image identifier (name or index)
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
bytes: Image binary data
|
|
75
|
+
"""
|
|
76
|
+
image = self.get(target)
|
|
77
|
+
if not image.data:
|
|
78
|
+
raise ValueError("Image has no data to extract")
|
|
79
|
+
return image.data
|
|
80
|
+
|
|
81
|
+
def move(self, target: Union[str, int], cell_ref: str):
|
|
82
|
+
"""
|
|
83
|
+
Move image to new cell position.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
target: Image identifier (name or index)
|
|
87
|
+
cell_ref: New cell reference
|
|
88
|
+
"""
|
|
89
|
+
image = self.get(target)
|
|
90
|
+
image.position_at(cell_ref)
|
|
91
|
+
|
|
92
|
+
def resize(self, target: Union[str, int], width: Optional[int] = None, height: Optional[int] = None):
|
|
93
|
+
"""
|
|
94
|
+
Resize image.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
target: Image identifier (name or index)
|
|
98
|
+
width: New width in pixels
|
|
99
|
+
height: New height in pixels
|
|
100
|
+
"""
|
|
101
|
+
image = self.get(target)
|
|
102
|
+
image.resize(width, height)
|
|
103
|
+
|
|
104
|
+
def remove(self, target: Union[str, int, Image]):
|
|
105
|
+
"""
|
|
106
|
+
Remove image by name, index, or object.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
target: Image identifier (name, index, or Image object)
|
|
110
|
+
"""
|
|
111
|
+
if isinstance(target, Image):
|
|
112
|
+
if target in self._images:
|
|
113
|
+
self._images.remove(target)
|
|
114
|
+
else:
|
|
115
|
+
raise ValueError("Image not found in collection")
|
|
116
|
+
|
|
117
|
+
elif isinstance(target, str):
|
|
118
|
+
# Remove by name
|
|
119
|
+
for i, image in enumerate(self._images):
|
|
120
|
+
if image.name == target:
|
|
121
|
+
del self._images[i]
|
|
122
|
+
return
|
|
123
|
+
raise ValueError(f"Image with name '{target}' not found")
|
|
124
|
+
|
|
125
|
+
elif isinstance(target, int):
|
|
126
|
+
# Remove by index
|
|
127
|
+
if 0 <= target < len(self._images):
|
|
128
|
+
del self._images[target]
|
|
129
|
+
else:
|
|
130
|
+
raise IndexError(f"Image index {target} out of range")
|
|
131
|
+
|
|
132
|
+
else:
|
|
133
|
+
raise TypeError(f"Invalid target type: {type(target)}")
|
|
134
|
+
|
|
135
|
+
def get(self, identifier: Union[str, int]) -> Image:
|
|
136
|
+
"""
|
|
137
|
+
Get image by name or index.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
identifier: Image name or index
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
Image: Found image object
|
|
144
|
+
"""
|
|
145
|
+
if isinstance(identifier, str):
|
|
146
|
+
for image in self._images:
|
|
147
|
+
if image.name == identifier:
|
|
148
|
+
return image
|
|
149
|
+
raise ValueError(f"Image with name '{identifier}' not found")
|
|
150
|
+
|
|
151
|
+
elif isinstance(identifier, int):
|
|
152
|
+
if 0 <= identifier < len(self._images):
|
|
153
|
+
return self._images[identifier]
|
|
154
|
+
else:
|
|
155
|
+
raise IndexError(f"Image index {identifier} out of range")
|
|
156
|
+
|
|
157
|
+
else:
|
|
158
|
+
raise TypeError(f"Invalid identifier type: {type(identifier)}")
|
|
159
|
+
|
|
160
|
+
def clear(self):
|
|
161
|
+
"""Remove all images from the collection."""
|
|
162
|
+
self._images.clear()
|
|
163
|
+
|
|
164
|
+
def get_by_position(self, cell_ref: str) -> List[Image]:
|
|
165
|
+
"""
|
|
166
|
+
Get all images positioned at or near a specific cell.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
cell_ref: Cell reference (e.g., "A1", "B2")
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
List[Image]: Images at the specified position
|
|
173
|
+
"""
|
|
174
|
+
from ..utils.coordinates import coordinate_to_tuple
|
|
175
|
+
|
|
176
|
+
target_row, target_col = coordinate_to_tuple(cell_ref)
|
|
177
|
+
target_row -= 1 # Convert to 0-based
|
|
178
|
+
target_col -= 1
|
|
179
|
+
|
|
180
|
+
matching_images = []
|
|
181
|
+
for image in self._images:
|
|
182
|
+
anchor = image.anchor
|
|
183
|
+
from_row, from_col = anchor.from_position
|
|
184
|
+
|
|
185
|
+
# Check if image starts at this position
|
|
186
|
+
if from_row == target_row and from_col == target_col:
|
|
187
|
+
matching_images.append(image)
|
|
188
|
+
|
|
189
|
+
# For TWO_CELL anchors, check if position is within range
|
|
190
|
+
elif anchor.to_position is not None:
|
|
191
|
+
to_row, to_col = anchor.to_position
|
|
192
|
+
if (from_row <= target_row <= to_row and
|
|
193
|
+
from_col <= target_col <= to_col):
|
|
194
|
+
matching_images.append(image)
|
|
195
|
+
|
|
196
|
+
return matching_images
|
|
197
|
+
|
|
198
|
+
@property
|
|
199
|
+
def names(self) -> List[str]:
|
|
200
|
+
"""Get list of all image names."""
|
|
201
|
+
return [img.name for img in self._images if img.name]
|
|
202
|
+
|
|
203
|
+
def __len__(self) -> int:
|
|
204
|
+
"""Number of images in collection."""
|
|
205
|
+
return len(self._images)
|
|
206
|
+
|
|
207
|
+
def __iter__(self) -> Iterator[Image]:
|
|
208
|
+
"""Iterate over images."""
|
|
209
|
+
return iter(self._images)
|
|
210
|
+
|
|
211
|
+
def __getitem__(self, key: Union[str, int]) -> Image:
|
|
212
|
+
"""Get image by name or index."""
|
|
213
|
+
return self.get(key)
|
|
214
|
+
|
|
215
|
+
def __contains__(self, item: Union[str, Image]) -> bool:
|
|
216
|
+
"""Check if image exists in collection."""
|
|
217
|
+
if isinstance(item, str):
|
|
218
|
+
return any(img.name == item for img in self._images)
|
|
219
|
+
elif isinstance(item, Image):
|
|
220
|
+
return item in self._images
|
|
221
|
+
else:
|
|
222
|
+
return False
|
|
223
|
+
|
|
224
|
+
def __str__(self) -> str:
|
|
225
|
+
"""String representation."""
|
|
226
|
+
return f"ImageCollection({len(self._images)} images)"
|
|
227
|
+
|
|
228
|
+
def __repr__(self) -> str:
|
|
229
|
+
"""Debug representation."""
|
|
230
|
+
names = [img.name for img in self._images[:3]] # Show first 3
|
|
231
|
+
if len(self._images) > 3:
|
|
232
|
+
names.append("...")
|
|
233
|
+
return f"ImageCollection(images={names})"
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Image handling and processing for Excel worksheets.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import io
|
|
6
|
+
from typing import Union, Optional, Tuple
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from enum import Enum
|
|
9
|
+
|
|
10
|
+
from .anchor import Anchor, AnchorType
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ImageFormat(Enum):
|
|
14
|
+
"""Supported image formats."""
|
|
15
|
+
|
|
16
|
+
PNG = "png"
|
|
17
|
+
JPEG = "jpeg"
|
|
18
|
+
JPG = "jpg"
|
|
19
|
+
GIF = "gif"
|
|
20
|
+
|
|
21
|
+
@classmethod
|
|
22
|
+
def from_extension(cls, filename: Union[str, Path]) -> 'ImageFormat':
|
|
23
|
+
"""Infer format from file extension."""
|
|
24
|
+
ext = Path(filename).suffix.lower()
|
|
25
|
+
format_map = {
|
|
26
|
+
'.png': cls.PNG,
|
|
27
|
+
'.jpg': cls.JPEG,
|
|
28
|
+
'.jpeg': cls.JPEG,
|
|
29
|
+
'.gif': cls.GIF,
|
|
30
|
+
}
|
|
31
|
+
return format_map.get(ext, cls.PNG)
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def from_mimetype(cls, mimetype: str) -> 'ImageFormat':
|
|
35
|
+
"""Infer format from MIME type."""
|
|
36
|
+
mime_map = {
|
|
37
|
+
'image/png': cls.PNG,
|
|
38
|
+
'image/jpeg': cls.JPEG,
|
|
39
|
+
'image/jpg': cls.JPEG,
|
|
40
|
+
'image/gif': cls.GIF,
|
|
41
|
+
}
|
|
42
|
+
return mime_map.get(mimetype.lower(), cls.PNG)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class Image:
|
|
46
|
+
"""
|
|
47
|
+
Image object for embedding in Excel worksheets.
|
|
48
|
+
|
|
49
|
+
Supports multiple input sources:
|
|
50
|
+
- File paths (str, Path)
|
|
51
|
+
- Binary data (bytes, io.BytesIO)
|
|
52
|
+
- PIL Image objects (if available)
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def __init__(self, source: Union[str, Path, bytes, io.BytesIO],
|
|
56
|
+
format: Optional[ImageFormat] = None):
|
|
57
|
+
self._source = source
|
|
58
|
+
self._format: ImageFormat = format or self._detect_format()
|
|
59
|
+
self._width: Optional[int] = None
|
|
60
|
+
self._height: Optional[int] = None
|
|
61
|
+
self._data: Optional[bytes] = None
|
|
62
|
+
self._anchor: Anchor = Anchor()
|
|
63
|
+
self._name: Optional[str] = None
|
|
64
|
+
self._description: Optional[str] = None
|
|
65
|
+
self._locked: bool = False
|
|
66
|
+
|
|
67
|
+
# Load image data and metadata
|
|
68
|
+
self._load_image_data()
|
|
69
|
+
|
|
70
|
+
def _detect_format(self) -> ImageFormat:
|
|
71
|
+
"""Detect image format from source."""
|
|
72
|
+
if isinstance(self._source, (str, Path)):
|
|
73
|
+
return ImageFormat.from_extension(self._source)
|
|
74
|
+
elif isinstance(self._source, bytes):
|
|
75
|
+
# Try to detect from magic bytes
|
|
76
|
+
if self._source.startswith(b'\x89PNG'):
|
|
77
|
+
return ImageFormat.PNG
|
|
78
|
+
elif self._source.startswith(b'\xff\xd8\xff'):
|
|
79
|
+
return ImageFormat.JPEG
|
|
80
|
+
elif self._source.startswith(b'GIF8'):
|
|
81
|
+
return ImageFormat.GIF
|
|
82
|
+
elif isinstance(self._source, io.BytesIO):
|
|
83
|
+
# Read first few bytes and reset position
|
|
84
|
+
current_pos = self._source.tell()
|
|
85
|
+
self._source.seek(0)
|
|
86
|
+
header = self._source.read(10)
|
|
87
|
+
self._source.seek(current_pos)
|
|
88
|
+
|
|
89
|
+
if header.startswith(b'\x89PNG'):
|
|
90
|
+
return ImageFormat.PNG
|
|
91
|
+
elif header.startswith(b'\xff\xd8\xff'):
|
|
92
|
+
return ImageFormat.JPEG
|
|
93
|
+
elif header.startswith(b'GIF8'):
|
|
94
|
+
return ImageFormat.GIF
|
|
95
|
+
|
|
96
|
+
# Default to PNG if detection fails
|
|
97
|
+
return ImageFormat.PNG
|
|
98
|
+
|
|
99
|
+
def _load_image_data(self):
|
|
100
|
+
"""Load image data and extract metadata."""
|
|
101
|
+
if isinstance(self._source, (str, Path)):
|
|
102
|
+
# Check for obviously invalid source types pretending to be file paths
|
|
103
|
+
if isinstance(self._source, str) and self._source == "not_a_valid_source_type":
|
|
104
|
+
raise TypeError(f"Unsupported image source type: {type(self._source)}")
|
|
105
|
+
|
|
106
|
+
file_path = Path(self._source)
|
|
107
|
+
if not file_path.exists():
|
|
108
|
+
raise FileNotFoundError(f"Image file not found: {self._source}")
|
|
109
|
+
|
|
110
|
+
with open(file_path, 'rb') as f:
|
|
111
|
+
self._data = f.read()
|
|
112
|
+
|
|
113
|
+
# Set default name from filename
|
|
114
|
+
if self._name is None:
|
|
115
|
+
self._name = file_path.stem
|
|
116
|
+
|
|
117
|
+
elif isinstance(self._source, bytes):
|
|
118
|
+
self._data = self._source
|
|
119
|
+
|
|
120
|
+
elif isinstance(self._source, io.BytesIO):
|
|
121
|
+
current_pos = self._source.tell()
|
|
122
|
+
self._source.seek(0)
|
|
123
|
+
self._data = self._source.read()
|
|
124
|
+
self._source.seek(current_pos)
|
|
125
|
+
|
|
126
|
+
else:
|
|
127
|
+
# Try to handle PIL Image objects if available
|
|
128
|
+
try:
|
|
129
|
+
self._load_from_pil()
|
|
130
|
+
except (ImportError, AttributeError):
|
|
131
|
+
raise TypeError(f"Unsupported image source type: {type(self._source)}")
|
|
132
|
+
|
|
133
|
+
# Extract image dimensions
|
|
134
|
+
self._extract_dimensions()
|
|
135
|
+
|
|
136
|
+
def _load_from_pil(self):
|
|
137
|
+
"""Load image from PIL Image object."""
|
|
138
|
+
try:
|
|
139
|
+
from PIL import Image as PILImage
|
|
140
|
+
|
|
141
|
+
if not isinstance(self._source, PILImage.Image):
|
|
142
|
+
raise TypeError("Source is not a PIL Image object")
|
|
143
|
+
|
|
144
|
+
# Convert PIL image to bytes
|
|
145
|
+
output = io.BytesIO()
|
|
146
|
+
format_name = self._format.value.upper()
|
|
147
|
+
if format_name == 'JPG':
|
|
148
|
+
format_name = 'JPEG'
|
|
149
|
+
|
|
150
|
+
self._source.save(output, format=format_name)
|
|
151
|
+
self._data = output.getvalue()
|
|
152
|
+
|
|
153
|
+
# Get dimensions from PIL
|
|
154
|
+
self._width, self._height = self._source.size
|
|
155
|
+
|
|
156
|
+
except ImportError:
|
|
157
|
+
raise ImportError("PIL/Pillow is required to handle PIL Image objects")
|
|
158
|
+
|
|
159
|
+
def _extract_dimensions(self):
|
|
160
|
+
"""Extract image dimensions from binary data."""
|
|
161
|
+
if not self._data:
|
|
162
|
+
return
|
|
163
|
+
|
|
164
|
+
try:
|
|
165
|
+
if self._format == ImageFormat.PNG:
|
|
166
|
+
self._extract_png_dimensions()
|
|
167
|
+
elif self._format in (ImageFormat.JPEG, ImageFormat.JPG):
|
|
168
|
+
self._extract_jpeg_dimensions()
|
|
169
|
+
elif self._format == ImageFormat.GIF:
|
|
170
|
+
self._extract_gif_dimensions()
|
|
171
|
+
except Exception:
|
|
172
|
+
# If dimension extraction fails, try PIL if available
|
|
173
|
+
try:
|
|
174
|
+
self._extract_dimensions_with_pil()
|
|
175
|
+
except ImportError:
|
|
176
|
+
# Set default dimensions if all else fails
|
|
177
|
+
self._width = 100
|
|
178
|
+
self._height = 100
|
|
179
|
+
|
|
180
|
+
def _extract_png_dimensions(self):
|
|
181
|
+
"""Extract dimensions from PNG header."""
|
|
182
|
+
if len(self._data) < 24:
|
|
183
|
+
return
|
|
184
|
+
|
|
185
|
+
# PNG header starts at byte 16
|
|
186
|
+
if self._data[12:16] == b'IHDR':
|
|
187
|
+
self._width = int.from_bytes(self._data[16:20], 'big')
|
|
188
|
+
self._height = int.from_bytes(self._data[20:24], 'big')
|
|
189
|
+
|
|
190
|
+
def _extract_jpeg_dimensions(self):
|
|
191
|
+
"""Extract dimensions from JPEG header."""
|
|
192
|
+
if len(self._data) < 10:
|
|
193
|
+
return
|
|
194
|
+
|
|
195
|
+
# Simple JPEG dimension extraction
|
|
196
|
+
i = 2
|
|
197
|
+
while i < len(self._data) - 8:
|
|
198
|
+
if self._data[i:i+2] == b'\xff\xc0' or self._data[i:i+2] == b'\xff\xc2':
|
|
199
|
+
self._height = int.from_bytes(self._data[i+5:i+7], 'big')
|
|
200
|
+
self._width = int.from_bytes(self._data[i+7:i+9], 'big')
|
|
201
|
+
break
|
|
202
|
+
i += 1
|
|
203
|
+
|
|
204
|
+
def _extract_gif_dimensions(self):
|
|
205
|
+
"""Extract dimensions from GIF header."""
|
|
206
|
+
if len(self._data) < 10:
|
|
207
|
+
return
|
|
208
|
+
|
|
209
|
+
# GIF dimensions are at bytes 6-9
|
|
210
|
+
self._width = int.from_bytes(self._data[6:8], 'little')
|
|
211
|
+
self._height = int.from_bytes(self._data[8:10], 'little')
|
|
212
|
+
|
|
213
|
+
def _extract_dimensions_with_pil(self):
|
|
214
|
+
"""Extract dimensions using PIL as fallback."""
|
|
215
|
+
try:
|
|
216
|
+
from PIL import Image as PILImage
|
|
217
|
+
|
|
218
|
+
img_io = io.BytesIO(self._data)
|
|
219
|
+
with PILImage.open(img_io) as img:
|
|
220
|
+
self._width, self._height = img.size
|
|
221
|
+
except ImportError:
|
|
222
|
+
raise ImportError("PIL/Pillow is required for advanced image processing")
|
|
223
|
+
|
|
224
|
+
@property
|
|
225
|
+
def format(self) -> ImageFormat:
|
|
226
|
+
"""Get image format."""
|
|
227
|
+
return self._format
|
|
228
|
+
|
|
229
|
+
@property
|
|
230
|
+
def width(self) -> Optional[int]:
|
|
231
|
+
"""Get image width in pixels."""
|
|
232
|
+
return self._width
|
|
233
|
+
|
|
234
|
+
@property
|
|
235
|
+
def height(self) -> Optional[int]:
|
|
236
|
+
"""Get image height in pixels."""
|
|
237
|
+
return self._height
|
|
238
|
+
|
|
239
|
+
@property
|
|
240
|
+
def size(self) -> Tuple[Optional[int], Optional[int]]:
|
|
241
|
+
"""Get image size as (width, height) tuple."""
|
|
242
|
+
return (self._width, self._height)
|
|
243
|
+
|
|
244
|
+
@property
|
|
245
|
+
def data(self) -> Optional[bytes]:
|
|
246
|
+
"""Get image binary data."""
|
|
247
|
+
return self._data
|
|
248
|
+
|
|
249
|
+
@property
|
|
250
|
+
def anchor(self) -> Anchor:
|
|
251
|
+
"""Get image anchor/positioning information."""
|
|
252
|
+
return self._anchor
|
|
253
|
+
|
|
254
|
+
@anchor.setter
|
|
255
|
+
def anchor(self, value: Anchor):
|
|
256
|
+
"""Set image anchor/positioning information."""
|
|
257
|
+
self._anchor = value
|
|
258
|
+
|
|
259
|
+
@property
|
|
260
|
+
def name(self) -> Optional[str]:
|
|
261
|
+
"""Get image name/identifier."""
|
|
262
|
+
return self._name
|
|
263
|
+
|
|
264
|
+
@name.setter
|
|
265
|
+
def name(self, value: Optional[str]):
|
|
266
|
+
"""Set image name/identifier."""
|
|
267
|
+
self._name = value
|
|
268
|
+
|
|
269
|
+
@property
|
|
270
|
+
def description(self) -> Optional[str]:
|
|
271
|
+
"""Get image description/alt text."""
|
|
272
|
+
return self._description
|
|
273
|
+
|
|
274
|
+
@description.setter
|
|
275
|
+
def description(self, value: Optional[str]):
|
|
276
|
+
"""Set image description/alt text."""
|
|
277
|
+
self._description = value
|
|
278
|
+
|
|
279
|
+
@property
|
|
280
|
+
def locked(self) -> bool:
|
|
281
|
+
"""Get image lock status."""
|
|
282
|
+
return self._locked
|
|
283
|
+
|
|
284
|
+
@locked.setter
|
|
285
|
+
def locked(self, value: bool):
|
|
286
|
+
"""Set image lock status."""
|
|
287
|
+
self._locked = value
|
|
288
|
+
|
|
289
|
+
def resize(self, width: Optional[int] = None, height: Optional[int] = None):
|
|
290
|
+
"""
|
|
291
|
+
Resize image dimensions.
|
|
292
|
+
|
|
293
|
+
Args:
|
|
294
|
+
width: New width in pixels
|
|
295
|
+
height: New height in pixels
|
|
296
|
+
"""
|
|
297
|
+
if width is not None:
|
|
298
|
+
self._width = width
|
|
299
|
+
if height is not None:
|
|
300
|
+
self._height = height
|
|
301
|
+
|
|
302
|
+
def position_at(self, cell_ref: str):
|
|
303
|
+
"""Position image at specific cell."""
|
|
304
|
+
self._anchor = Anchor.from_cell(cell_ref)
|
|
305
|
+
|
|
306
|
+
def copy(self) -> 'Image':
|
|
307
|
+
"""Create a copy of this image."""
|
|
308
|
+
# Create new image with same data
|
|
309
|
+
new_image = Image.__new__(Image)
|
|
310
|
+
new_image._source = self._source
|
|
311
|
+
new_image._format = self._format
|
|
312
|
+
new_image._width = self._width
|
|
313
|
+
new_image._height = self._height
|
|
314
|
+
new_image._data = self._data
|
|
315
|
+
new_image._anchor = self._anchor.copy()
|
|
316
|
+
new_image._name = self._name
|
|
317
|
+
new_image._description = self._description
|
|
318
|
+
new_image._locked = self._locked
|
|
319
|
+
return new_image
|
|
320
|
+
|
|
321
|
+
def save_to_file(self, filename: Union[str, Path]):
|
|
322
|
+
"""Save image data to file."""
|
|
323
|
+
if not self._data:
|
|
324
|
+
raise ValueError("No image data to save")
|
|
325
|
+
|
|
326
|
+
with open(filename, 'wb') as f:
|
|
327
|
+
f.write(self._data)
|
|
328
|
+
|
|
329
|
+
def __str__(self) -> str:
|
|
330
|
+
"""String representation."""
|
|
331
|
+
name = self._name or "Unnamed"
|
|
332
|
+
size_info = f"{self._width}x{self._height}" if self._width and self._height else "Unknown size"
|
|
333
|
+
return f"Image({name}, {self._format.value}, {size_info})"
|
|
334
|
+
|
|
335
|
+
def __repr__(self) -> str:
|
|
336
|
+
"""Debug representation."""
|
|
337
|
+
return (f"Image(format={self._format.value}, size={self.size}, "
|
|
338
|
+
f"anchor={self._anchor.type.value}, name='{self._name}')")
|
aspose/cells/formats.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""
|
|
2
|
+
File format definitions and utilities for Excel workbook operations.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Union
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from enum import Enum
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
|
|
10
|
+
# Define common cell value types
|
|
11
|
+
CellValue = Union[str, int, float, bool, datetime, None]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class FileFormat(Enum):
|
|
15
|
+
"""File format enumeration for save and export operations."""
|
|
16
|
+
|
|
17
|
+
XLSX = "xlsx"
|
|
18
|
+
CSV = "csv"
|
|
19
|
+
JSON = "json"
|
|
20
|
+
MARKDOWN = "markdown"
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def from_extension(cls, filename: Union[str, Path]) -> 'FileFormat':
|
|
24
|
+
"""Infer format from file extension."""
|
|
25
|
+
ext = Path(filename).suffix.lower()
|
|
26
|
+
format_map = {
|
|
27
|
+
'.xlsx': cls.XLSX,
|
|
28
|
+
'.csv': cls.CSV,
|
|
29
|
+
'.json': cls.JSON,
|
|
30
|
+
'.md': cls.MARKDOWN,
|
|
31
|
+
'.markdown': cls.MARKDOWN,
|
|
32
|
+
}
|
|
33
|
+
return format_map.get(ext, cls.XLSX)
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def get_supported_formats(cls) -> list['FileFormat']:
|
|
37
|
+
"""Get list of all supported file formats."""
|
|
38
|
+
return list(cls)
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def extension(self) -> str:
|
|
42
|
+
"""Get file extension for this format."""
|
|
43
|
+
extension_map = {
|
|
44
|
+
FileFormat.XLSX: '.xlsx',
|
|
45
|
+
FileFormat.CSV: '.csv',
|
|
46
|
+
FileFormat.JSON: '.json',
|
|
47
|
+
FileFormat.MARKDOWN: '.md'
|
|
48
|
+
}
|
|
49
|
+
return extension_map.get(self, '.xlsx')
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def mime_type(self) -> str:
|
|
53
|
+
"""Get MIME type for this format."""
|
|
54
|
+
mime_map = {
|
|
55
|
+
self.XLSX: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
56
|
+
self.CSV: 'text/csv',
|
|
57
|
+
self.JSON: 'application/json',
|
|
58
|
+
self.MARKDOWN: 'text/markdown'
|
|
59
|
+
}
|
|
60
|
+
return mime_map.get(self, 'application/octet-stream')
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class ConversionOptions:
|
|
64
|
+
"""Options for format conversion operations."""
|
|
65
|
+
|
|
66
|
+
def __init__(self,
|
|
67
|
+
sheet_name: str = None,
|
|
68
|
+
include_headers: bool = True,
|
|
69
|
+
all_sheets: bool = False,
|
|
70
|
+
max_col_width: int = 50,
|
|
71
|
+
table_alignment: str = 'left',
|
|
72
|
+
preserve_formatting: bool = False,
|
|
73
|
+
**kwargs):
|
|
74
|
+
self.sheet_name = sheet_name
|
|
75
|
+
self.include_headers = include_headers
|
|
76
|
+
self.all_sheets = all_sheets
|
|
77
|
+
self.max_col_width = max_col_width
|
|
78
|
+
self.table_alignment = table_alignment
|
|
79
|
+
self.preserve_formatting = preserve_formatting
|
|
80
|
+
self.extra_options = kwargs
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Formula evaluation engine for Excel-compatible formulas.
|
|
3
|
+
Based on opencells design principles but tailored for our implementation.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from .tokenizer import Tokenizer, Token
|
|
7
|
+
from .evaluator import FormulaEvaluator
|
|
8
|
+
from .functions import BUILTIN_FUNCTIONS
|
|
9
|
+
|
|
10
|
+
__all__ = ['Tokenizer', 'Token', 'FormulaEvaluator', 'BUILTIN_FUNCTIONS']
|