From a440496fc587cc1482867795f0b3f8537b0d80b0 Mon Sep 17 00:00:00 2001 From: pappnu Date: Tue, 12 Aug 2025 20:21:12 +0300 Subject: [PATCH 01/12] feat: Add extensive type annotations, add classes for path handling, add various missing properties BREAKING CHANGE: Uses syntax that requires Python 3.10, removes echo, compareWithNumbers and system functions, which simply wrapped basic Python constructs without adding any extra functionality, and makes getByName return None instead of throwing when no match is found. --- examples/active_layer.py | 4 +- examples/add_metadata.py | 2 +- ...ange_color_of_background_and_foreground.py | 4 +- examples/compare_colors.py | 2 +- examples/convert_smartobject_to_layer.py | 6 +- examples/current_tool.py | 2 +- examples/eval_javascript.py | 2 +- ...rt_layers_use_export_options_saveforweb.py | 2 +- examples/get_document_by_name.py | 4 +- examples/get_layer_by_name.py | 2 +- examples/link_layer.py | 6 +- examples/open_psd.py | 2 +- examples/operate_channels.py | 2 +- examples/operate_layerSet.py | 4 +- examples/session_callback.py | 2 +- examples/session_new_document.py | 2 +- photoshop/api/_active_layer.py | 18 - photoshop/api/_artlayer.py | 385 +++++++----------- photoshop/api/_artlayers.py | 76 +--- photoshop/api/_channel.py | 48 ++- photoshop/api/_channels.py | 55 +-- photoshop/api/_core.py | 92 +++-- photoshop/api/_document.py | 311 ++++++++------ photoshop/api/_documentinfo.py | 104 ++--- photoshop/api/_documents.py | 47 +-- photoshop/api/_layer.py | 150 +++++++ photoshop/api/_layerComp.py | 56 +-- photoshop/api/_layerComps.py | 71 ++-- photoshop/api/_layerSet.py | 132 ++---- photoshop/api/_layerSets.py | 71 +--- photoshop/api/_layers.py | 56 +-- photoshop/api/_measurement_log.py | 14 +- photoshop/api/_notifier.py | 13 +- photoshop/api/_notifiers.py | 40 +- photoshop/api/_preferences.py | 368 +++++++++++++++-- photoshop/api/_selection.py | 141 ++++--- photoshop/api/_text_fonts.py | 41 +- photoshop/api/action_descriptor.py | 170 ++------ photoshop/api/action_list.py | 107 +++-- photoshop/api/action_reference.py | 50 ++- photoshop/api/application.py | 172 ++++---- photoshop/api/base_action.py | 141 +++++++ photoshop/api/batch_options.py | 60 +-- photoshop/api/collections.py | 91 +++++ photoshop/api/colors/cmyk.py | 26 +- photoshop/api/colors/gray.py | 4 +- photoshop/api/colors/hsb.py | 20 +- photoshop/api/colors/lab.py | 20 +- photoshop/api/colors/rgb.py | 26 +- photoshop/api/enumerations.py | 12 +- photoshop/api/open_options/eps.py | 47 ++- photoshop/api/path_item.py | 100 +++++ photoshop/api/path_items.py | 27 ++ photoshop/api/path_point.py | 34 ++ photoshop/api/path_point_info.py | 73 ++++ photoshop/api/protocols.py | 31 ++ photoshop/api/save_options/bmp.py | 39 +- photoshop/api/save_options/eps.py | 39 +- photoshop/api/save_options/gif.py | 47 +-- photoshop/api/save_options/jpg.py | 33 +- photoshop/api/save_options/pdf.py | 268 ++++++++---- photoshop/api/save_options/png.py | 110 ++++- photoshop/api/save_options/psd.py | 22 +- photoshop/api/save_options/tag.py | 16 +- photoshop/api/save_options/tif.py | 61 +-- photoshop/api/solid_color.py | 18 +- photoshop/api/sub_path_info.py | 48 +++ photoshop/api/sub_path_item.py | 32 ++ photoshop/api/text_font.py | 8 +- photoshop/api/text_item.py | 262 ++++++------ photoshop/session.py | 115 +++--- test/manual_test/manual_test_application.py | 24 +- test/manual_test/manual_test_new_document.py | 78 ++++ test/manual_test/manual_test_path_items.py | 78 ++++ test/manual_test/manual_test_text_item.py | 18 +- 75 files changed, 3048 insertions(+), 1816 deletions(-) delete mode 100644 photoshop/api/_active_layer.py create mode 100644 photoshop/api/_layer.py create mode 100644 photoshop/api/base_action.py create mode 100644 photoshop/api/collections.py create mode 100644 photoshop/api/path_item.py create mode 100644 photoshop/api/path_items.py create mode 100644 photoshop/api/path_point.py create mode 100644 photoshop/api/path_point_info.py create mode 100644 photoshop/api/protocols.py create mode 100644 photoshop/api/sub_path_info.py create mode 100644 photoshop/api/sub_path_item.py create mode 100644 test/manual_test/manual_test_new_document.py create mode 100644 test/manual_test/manual_test_path_items.py diff --git a/examples/active_layer.py b/examples/active_layer.py index 10a82fca..3c3ab1c2 100644 --- a/examples/active_layer.py +++ b/examples/active_layer.py @@ -28,9 +28,9 @@ docRef.artLayers.add() # Display current active layer name - ps.echo(docRef.activeLayer.name) + print(docRef.activeLayer.name) # Create and rename a new layer new_layer = docRef.artLayers.add() - ps.echo(new_layer.name) + print(new_layer.name) new_layer.name = "test" diff --git a/examples/add_metadata.py b/examples/add_metadata.py index 63d471bd..a8369db2 100644 --- a/examples/add_metadata.py +++ b/examples/add_metadata.py @@ -13,4 +13,4 @@ doc.info.provinceState = "Beijing" doc.info.title = "My Demo" print("Print all metadata of current active document.") - ps.echo(doc.info) + print(doc.info) diff --git a/examples/change_color_of_background_and_foreground.py b/examples/change_color_of_background_and_foreground.py index 8263f90a..8f78f651 100644 --- a/examples/change_color_of_background_and_foreground.py +++ b/examples/change_color_of_background_and_foreground.py @@ -34,10 +34,10 @@ ps.app.backgroundColor = bg_color # Print current colors - ps.echo(f"Foreground RGB: {ps.app.foregroundColor.rgb.red}, " + print(f"Foreground RGB: {ps.app.foregroundColor.rgb.red}, " f"{ps.app.foregroundColor.rgb.green}, " f"{ps.app.foregroundColor.rgb.blue}") - ps.echo(f"Background RGB: {ps.app.backgroundColor.rgb.red}, " + print(f"Background RGB: {ps.app.backgroundColor.rgb.red}, " f"{ps.app.backgroundColor.rgb.green}, " f"{ps.app.backgroundColor.rgb.blue}") diff --git a/examples/compare_colors.py b/examples/compare_colors.py index ab1f1e3b..5a430d57 100644 --- a/examples/compare_colors.py +++ b/examples/compare_colors.py @@ -34,4 +34,4 @@ color1.rgb.green == color2.rgb.green and color1.rgb.blue == color2.rgb.blue) - ps.echo(f"Colors are {'same' if is_same else 'different'}") + print(f"Colors are {'same' if is_same else 'different'}") diff --git a/examples/convert_smartobject_to_layer.py b/examples/convert_smartobject_to_layer.py index 12368bb8..428df623 100644 --- a/examples/convert_smartobject_to_layer.py +++ b/examples/convert_smartobject_to_layer.py @@ -26,12 +26,12 @@ # Convert to smart object layer.convertToSmartObject() - ps.echo("Layer converted to Smart Object") + print("Layer converted to Smart Object") # Check if it's a smart object if layer.kind == ps.LayerKind.SmartObjectLayer: - ps.echo("Layer is now a Smart Object") + print("Layer is now a Smart Object") # Convert back to regular layer layer.rasterize(ps.RasterizeType.EntireLayer) - ps.echo("Smart Object converted back to regular layer") + print("Smart Object converted back to regular layer") diff --git a/examples/current_tool.py b/examples/current_tool.py index 949929e0..0b13289f 100644 --- a/examples/current_tool.py +++ b/examples/current_tool.py @@ -22,4 +22,4 @@ current = ps.app.currentTool # Print current tool name - ps.echo(f"Current tool: {current}") + print(f"Current tool: {current}") diff --git a/examples/eval_javascript.py b/examples/eval_javascript.py index 805a643b..59ec5ccd 100644 --- a/examples/eval_javascript.py +++ b/examples/eval_javascript.py @@ -21,4 +21,4 @@ # Execute JavaScript command js_code = "app.documents.length" result = ps.app.eval_javascript(js_code) - ps.echo(f"Number of open documents: {result}") + print(f"Number of open documents: {result}") diff --git a/examples/export_layers_use_export_options_saveforweb.py b/examples/export_layers_use_export_options_saveforweb.py index aa84ecc2..00c89b76 100644 --- a/examples/export_layers_use_export_options_saveforweb.py +++ b/examples/export_layers_use_export_options_saveforweb.py @@ -33,7 +33,7 @@ def main(): image_path = os.path.join(layer_path, f"{layer.name}.png") doc.exportDocument(image_path, exportAs=ps.ExportType.SaveForWeb, options=options) ps.alert("Task done!") - ps.echo(doc.activeLayer) + print(doc.activeLayer) if __name__ == "__main__": diff --git a/examples/get_document_by_name.py b/examples/get_document_by_name.py index 6ca9c797..b50482ac 100644 --- a/examples/get_document_by_name.py +++ b/examples/get_document_by_name.py @@ -19,7 +19,7 @@ # Try to get document named 'test.psd' for doc in ps.app.documents: if doc.name == "test.psd": - ps.echo(doc.name) + print(doc.name) break else: - ps.echo("Document not found!") + print("Document not found!") diff --git a/examples/get_layer_by_name.py b/examples/get_layer_by_name.py index c287e621..92078784 100644 --- a/examples/get_layer_by_name.py +++ b/examples/get_layer_by_name.py @@ -20,5 +20,5 @@ doc = ps.app.activeDocument for layer in doc.layers: if layer.name == "example layer": - ps.echo(layer.name) + print(layer.name) break diff --git a/examples/link_layer.py b/examples/link_layer.py index 0a327955..de4a520a 100644 --- a/examples/link_layer.py +++ b/examples/link_layer.py @@ -35,9 +35,9 @@ layer2.link(layer3) # Check link status - ps.echo(f"Layer 1 linked: {layer1.linked}") - ps.echo(f"Layer 2 linked: {layer2.linked}") - ps.echo(f"Layer 3 linked: {layer3.linked}") + print(f"Layer 1 linked: {layer1.linked}") + print(f"Layer 2 linked: {layer2.linked}") + print(f"Layer 3 linked: {layer3.linked}") # Move linked layers together layer1.translate(100, 100) diff --git a/examples/open_psd.py b/examples/open_psd.py index ac03d4d7..ed6e0fb4 100644 --- a/examples/open_psd.py +++ b/examples/open_psd.py @@ -9,4 +9,4 @@ # style 2 with Session("your/psd/or/psb/file_path.psd", action="open") as ps: - ps.echo(ps.active_document.name) + print(ps.active_document.name) diff --git a/examples/operate_channels.py b/examples/operate_channels.py index d64becb3..84f1a126 100644 --- a/examples/operate_channels.py +++ b/examples/operate_channels.py @@ -22,7 +22,7 @@ # List all channels for channel in doc.channels: - ps.echo(f"Channel: {channel.name}") + print(f"Channel: {channel.name}") # Create a new alpha channel new_channel = doc.channels.add() diff --git a/examples/operate_layerSet.py b/examples/operate_layerSet.py index a4a4bb3f..67fdc595 100644 --- a/examples/operate_layerSet.py +++ b/examples/operate_layerSet.py @@ -41,10 +41,10 @@ # List layers in groups for layer in main_group.layers: - ps.echo(f"Layer in main group: {layer.name}") + print(f"Layer in main group: {layer.name}") for layer in sub_group.layers: - ps.echo(f"Layer in sub group: {layer.name}") + print(f"Layer in sub group: {layer.name}") # Move a layer between groups layer1.move(sub_group, ps.ElementPlacement.INSIDE) diff --git a/examples/session_callback.py b/examples/session_callback.py index 9cc2eaf6..cdd97d3c 100644 --- a/examples/session_callback.py +++ b/examples/session_callback.py @@ -23,4 +23,4 @@ def on_close(): with Session(callback=on_close) as ps: - ps.echo("Working in session...") + print("Working in session...") diff --git a/examples/session_new_document.py b/examples/session_new_document.py index 7e34c73f..bdbc03e4 100644 --- a/examples/session_new_document.py +++ b/examples/session_new_document.py @@ -4,4 +4,4 @@ with Session(action="new_document") as ps: - ps.echo(ps.active_document.name) + print(ps.active_document.name) diff --git a/photoshop/api/_active_layer.py b/photoshop/api/_active_layer.py deleted file mode 100644 index 16449766..00000000 --- a/photoshop/api/_active_layer.py +++ /dev/null @@ -1,18 +0,0 @@ -# Import local modules -from photoshop.api._core import Photoshop - - -class ActiveLayer(Photoshop): - """The selected layer.""" - - def __int__(self): - super().__init__() - - @property - def name(self) -> str: - """The name of the layer.""" - return self.active_layer.Typename - - def add(self): - """Adds an element.""" - self.app.ActiveDocument.ArtLayers.Add() diff --git a/photoshop/api/_artlayer.py b/photoshop/api/_artlayer.py index ae144f8e..604c7410 100644 --- a/photoshop/api/_artlayer.py +++ b/photoshop/api/_artlayer.py @@ -1,21 +1,35 @@ -# Import built-in modules -from typing import Any - # Import local modules +from os import PathLike + from photoshop.api._core import Photoshop -from photoshop.api.enumerations import RasterizeType +from photoshop.api._layer import Layer +from photoshop.api.enumerations import ( + CreateFields, + DepthMaource, + DisplacementMapType, + ElementPlacement, + EliminateFields, + Geometry, + LayerKind, + LensType, + NoiseDistribution, + OffsetUndefinedAreas, + RasterizeType, + TextureType, + UndefinedAreas, +) from photoshop.api.text_item import TextItem # pylint: disable=too-many-public-methods, too-many-arguments -class ArtLayer(Photoshop): +class ArtLayer(Layer): """An object within a document that contains the visual elements of the image (equivalent to a layer in the Adobe Photoshop application). """ - def __init__(self, parent: Any = None): + def __init__(self, parent: Photoshop | None = None) -> None: super().__init__(parent=parent) self._flag_as_method( "add", @@ -48,79 +62,39 @@ def __init__(self, parent: Any = None): "applyOceanRipple", "applyOffset", "applyPinch", - "delete", - "duplicate", "invert", - "link", "merge", - "move", "posterize", "rasterize", - "unlink", "convertToSmartObject", ) @property - def allLocked(self): - return self.app.allLocked - - @allLocked.setter - def allLocked(self, value): - self.app.allLocked = value - - @property - def blendMode(self): - return self.app.blendMode - - @blendMode.setter - def blendMode(self, mode): - self.app.blendMode = mode - - @property - def bounds(self): - """The bounding rectangle of the layer.""" - return self.app.bounds - - @property - def linkedLayers(self) -> list: - """Get all layers linked to this layer. - - Returns: - list: Layer objects""" - return [ArtLayer(layer) for layer in self.app.linkedLayers] - - @property - def name(self) -> str: - return self.app.name - - @name.setter - def name(self, text: str): - self.app.name = text - - @property - def fillOpacity(self): + def fillOpacity(self) -> float: """The interior opacity of the layer. Range: 0.0 to 100.0.""" return self.app.fillOpacity @fillOpacity.setter - def fillOpacity(self, value): + def fillOpacity(self, value: float) -> None: """The interior opacity of the layer. Range: 0.0 to 100.0.""" self.app.fillOpacity = value @property - def filterMaskDensity(self): + def filterMaskDensity(self) -> float: + """The density of the filter mask (between 0.0 and 100.0)""" return self.app.filterMaskDensity @filterMaskDensity.setter - def filterMaskDensity(self, value): + def filterMaskDensity(self, value: float) -> None: self.app.filterMaskDensity = value @property - def filterMaskFeather(self): + def filterMaskFeather(self) -> float: + """The feather of the filter mask (between 0.0 and 250.0)""" return self.app.filterMaskFeather @filterMaskFeather.setter - def filterMaskFeather(self, value): + def filterMaskFeather(self, value: float) -> None: self.app.filterMaskFeather = value @property @@ -129,97 +103,66 @@ def grouped(self) -> bool: return self.app.grouped @grouped.setter - def grouped(self, value): + def grouped(self, value: bool) -> None: self.app.grouped = value @property - def isBackgroundLayer(self): + def isBackgroundLayer(self) -> bool: """bool: If true, the layer is a background layer.""" return self.app.isBackgroundLayer @isBackgroundLayer.setter - def isBackgroundLayer(self, value): + def isBackgroundLayer(self, value: bool) -> None: self.app.isBackgroundLayer = value @property - def kind(self): + def kind(self) -> LayerKind: """Get the layer kind. Returns: LayerKind: The kind of this layer. """ - try: - js = """ - var ref = new ActionReference(); - ref.putEnumerated(charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt")); - var desc = executeActionGet(ref); - var layerType = desc.getInteger(stringIDToTypeID("layerKind")); - layerType; - """ - return int(self.eval_javascript(js)) - except Exception as e: - print(f"Error getting layer kind: {str(e)}") - return None + return LayerKind(self.app.kind) @kind.setter - def kind(self, layer_type): - """set the layer kind.""" - self.app.kind = layer_type + def kind(self, value: LayerKind) -> None: + self.app.kind = value @property - def layerMaskDensity(self): + def layerMaskDensity(self) -> float: """The density of the layer mask (between 0.0 and 100.0).""" return self.app.layerMaskDensity @layerMaskDensity.setter - def layerMaskDensity(self, value): + def layerMaskDensity(self, value: float) -> None: self.app.layerMaskDensity = value @property - def layerMaskFeather(self): + def layerMaskFeather(self) -> float: """The feather of the layer mask (between 0.0 and 250.0).""" return self.app.layerMaskFeather @layerMaskFeather.setter - def layerMaskFeather(self, value): + def layerMaskFeather(self, value: float) -> None: self.app.layerMaskFeather = value @property - def opacity(self): - """The master opacity of the layer.""" - return round(self.app.opacity) - - @opacity.setter - def opacity(self, value): - self.app.opacity = value - - @property - def parent(self): - """The object’s container.""" - return self.app.parent - - @parent.setter - def parent(self, value): - """Set the object’s container.""" - self.app.parent = value - - @property - def pixelsLocked(self): + def pixelsLocked(self) -> bool: """If true, the pixels in the layer’s image cannot be edited.""" return self.app.pixelsLocked @pixelsLocked.setter - def pixelsLocked(self, value): + def pixelsLocked(self, value: bool) -> None: self.app.pixelsLocked = value @property - def positionLocked(self): + def positionLocked(self) -> bool: """bool: If true, the pixels in the layer’s image cannot be moved within the layer.""" return self.app.positionLocked @positionLocked.setter - def positionLocked(self, value): + def positionLocked(self, value: bool) -> None: self.app.positionLocked = value @property @@ -233,49 +176,36 @@ def textItem(self) -> TextItem: return TextItem(self.app.textItem) @textItem.setter - def textItem(self, value): + def textItem(self, value: TextItem) -> None: self.app.textItem = value @property - def transparentPixelsLocked(self): + def transparentPixelsLocked(self) -> bool: return self.app.transparentPixelsLocked @transparentPixelsLocked.setter - def transparentPixelsLocked(self, value): + def transparentPixelsLocked(self, value: bool) -> None: self.app.transparentPixelsLocked = value @property - def vectorMaskDensity(self): + def vectorMaskDensity(self) -> float: + """The density of the vector mask (between 0.0 and 100.0)""" return self.app.vectorMaskDensity @vectorMaskDensity.setter - def vectorMaskDensity(self, value): + def vectorMaskDensity(self, value: float) -> None: self.app.vectorMaskDensity = value @property - def vectorMaskFeather(self): + def vectorMaskFeather(self) -> float: + """The feather of the vector mask (between 0.0 and 250.0)""" return self.app.vectorMaskFeather @vectorMaskFeather.setter - def vectorMaskFeather(self, value): + def vectorMaskFeather(self, value: float) -> None: self.app.vectorMaskFeather = value - @property - def visible(self): - return self.app.visible - - @visible.setter - def visible(self, value): - self.app.visible = value - - @property - def length(self): - return len(list(self.app)) - - def add(self): - return self.app.add() - - def adjustBrightnessContrast(self, brightness, contrast): + def adjustBrightnessContrast(self, brightness: int, contrast: int) -> None: """Adjusts the brightness and contrast. Args: @@ -283,15 +213,15 @@ def adjustBrightnessContrast(self, brightness, contrast): contrast (int): The contrast amount. Range: -100 to 100. """ - return self.app.adjustBrightnessContrast(brightness, contrast) + self.app.adjustBrightnessContrast(brightness, contrast) def adjustColorBalance( self, - shadows, - midtones, - highlights, - preserveLuminosity, - ): + shadows: tuple[int, int, int], + midtones: tuple[int, int, int], + highlights: tuple[int, int, int], + preserveLuminosity: bool, + ) -> None: """Adjusts the color balance of the layer’s component channels. Args: @@ -310,14 +240,14 @@ def adjustColorBalance( preserveLuminosity: If true, luminosity is preserved. """ - return self.app.adjustColorBalance( + self.app.adjustColorBalance( shadows, midtones, highlights, preserveLuminosity, ) - def adjustCurves(self, curveShape): + def adjustCurves(self, curveShape: list[tuple[float, float]]) -> None: """Adjusts the tonal range of the selected channel using up to fourteen points. @@ -330,16 +260,16 @@ def adjustCurves(self, curveShape): Returns: """ - return self.app.adjustCurves(curveShape) + self.app.adjustCurves(curveShape) def adjustLevels( self, - inputRangeStart, - inputRangeEnd, - inputRangeGamma, - outputRangeStart, - outputRangeEnd, - ): + inputRangeStart: int, + inputRangeEnd: int, + inputRangeGamma: float, + outputRangeStart: int, + outputRangeEnd: int, + ) -> None: """Adjusts levels of the selected channels. Args: @@ -352,7 +282,7 @@ def adjustLevels( Returns: """ - return self.app.adjustLevels( + self.app.adjustLevels( inputRangeStart, inputRangeEnd, inputRangeGamma, @@ -360,10 +290,12 @@ def adjustLevels( outputRangeEnd, ) - def applyAddNoise(self, amount, distribution, monochromatic): - return self.app.applyAddNoise(amount, distribution, monochromatic) + def applyAddNoise( + self, amount: float, distribution: NoiseDistribution, monochromatic: bool + ) -> None: + self.app.applyAddNoise(amount, distribution, monochromatic) - def applyDiffuseGlow(self, graininess, amount, clear_amount): + def applyDiffuseGlow(self, graininess: int, amount: int, clear_amount: int) -> None: """Applies the diffuse glow filter. Args: @@ -374,103 +306,107 @@ def applyDiffuseGlow(self, graininess, amount, clear_amount): Returns: """ - return self.app.applyDiffuseGlow(graininess, amount, clear_amount) + self.app.applyDiffuseGlow(graininess, amount, clear_amount) - def applyAverage(self): + def applyAverage(self) -> None: """Applies the average filter.""" - return self.app.applyAverage() + self.app.applyAverage() - def applyBlur(self): + def applyBlur(self) -> None: """Applies the blur filter.""" - return self.app.applyBlur() + self.app.applyBlur() - def applyBlurMore(self): + def applyBlurMore(self) -> None: """Applies the blur more filter.""" - return self.app.applyBlurMore() + self.app.applyBlurMore() - def applyClouds(self): + def applyClouds(self) -> None: """Applies the clouds filter.""" - return self.app.applyClouds() + self.app.applyClouds() - def applyCustomFilter(self, characteristics, scale, offset): + def applyCustomFilter( + self, characteristics: list[int], scale: int, offset: int + ) -> None: """Applies the custom filter.""" - return self.app.applyCustomFilter(characteristics, scale, offset) + self.app.applyCustomFilter(characteristics, scale, offset) - def applyDeInterlace(self, eliminateFields, createFields): + def applyDeInterlace( + self, eliminateFields: EliminateFields, createFields: CreateFields + ) -> None: """Applies the de-interlace filter.""" - return self.app.applyDeInterlace(eliminateFields, createFields) + self.app.applyDeInterlace(eliminateFields, createFields) - def applyDespeckle(self): - return self.app.applyDespeckle() + def applyDespeckle(self) -> None: + self.app.applyDespeckle() - def applyDifferenceClouds(self): + def applyDifferenceClouds(self) -> None: """Applies the difference clouds filter.""" - return self.app.applyDifferenceClouds() + self.app.applyDifferenceClouds() def applyDisplace( self, - horizontalScale, - verticalScale, - displacementType, - undefinedAreas, - displacementMapFile, - ): + horizontalScale: int, + verticalScale: int, + displacementType: DisplacementMapType, + undefinedAreas: UndefinedAreas, + displacementMapFile: str | PathLike[str], + ) -> None: """Applies the displace filter.""" - return self.app.applyDisplace( + self.app.applyDisplace( horizontalScale, verticalScale, displacementType, undefinedAreas, - displacementMapFile, + str(displacementMapFile), ) - def applyDustAndScratches(self, radius, threshold): + def applyDustAndScratches(self, radius: int, threshold: int) -> None: """Applies the dust and scratches filter.""" - return self.app.applyDustAndScratches(radius, threshold) + self.app.applyDustAndScratches(radius, threshold) - def applyGaussianBlur(self, radius): + def applyGaussianBlur(self, radius: float) -> None: """Applies the gaussian blur filter.""" - return self.app.applyGaussianBlur(radius) + self.app.applyGaussianBlur(radius) def applyGlassEffect( self, - distortion, - smoothness, - scaling, - invert, - texture, - textureFile, - ): - return self.app.applyGlassEffect( + distortion: int, + smoothness: int, + scaling: int, + invert: bool, + texture: TextureType, + textureFile: str | PathLike[str], + ) -> None: + self.app.applyGlassEffect( distortion, smoothness, scaling, invert, texture, - textureFile, + str(textureFile), ) - def applyHighPass(self, radius): + def applyHighPass(self, radius: float) -> None: """Applies the high pass filter.""" - return self.app.applyHighPass(radius) + self.app.applyHighPass(radius) def applyLensBlur( self, - source, - focalDistance, - invertDepthMap, - shape, - radius, - bladeCurvature, - rotation, - brightness, - threshold, - amount, - distribution, - monochromatic, - ): + source: DepthMaource, + focalDistance: int, + invertDepthMap: bool, + shape: Geometry, + radius: int, + bladeCurvature: int, + rotation: int, + brightness: int, + threshold: int, + amount: int, + distribution: NoiseDistribution, + monochromatic: bool, + ) -> None: """Apply the lens blur filter.""" - return self.app.applyLensBlur( + self.app.applyLensBlur( source, focalDistance, invertDepthMap, @@ -485,60 +421,54 @@ def applyLensBlur( monochromatic, ) - def applyLensFlare(self, brightness, flareCenter, lensType): - return self.app.applyLensFlare(brightness, flareCenter, lensType) + def applyLensFlare( + self, brightness: int, flareCenter: tuple[float, float], lensType: LensType + ) -> None: + self.app.applyLensFlare(brightness, flareCenter, lensType) - def applyMaximum(self, radius): + def applyMaximum(self, radius: float) -> None: self.app.applyMaximum(radius) - def applyMedianNoise(self, radius): + def applyMedianNoise(self, radius: float) -> None: self.app.applyMedianNoise(radius) - def applyMinimum(self, radius): + def applyMinimum(self, radius: float) -> None: self.app.applyMinimum(radius) - def applyMotionBlur(self, angle, radius): + def applyMotionBlur(self, angle: int, radius: float) -> None: self.app.applyMotionBlur(angle, radius) - def applyNTSC(self): + def applyNTSC(self) -> None: self.app.applyNTSC() - def applyOceanRipple(self, size, magnitude): + def applyOceanRipple(self, size: int, magnitude: int) -> None: self.app.applyOceanRipple(size, magnitude) - def applyOffset(self, horizontal, vertical, undefinedAreas): + def applyOffset( + self, horizontal: int, vertical: int, undefinedAreas: OffsetUndefinedAreas + ) -> None: self.app.applyOffset(horizontal, vertical, undefinedAreas) - def applyPinch(self, amount): + def applyPinch(self, amount: int) -> None: self.app.applyPinch(amount) - def remove(self): - """Removes this layer from the document.""" - self.app.delete() - - def rasterize(self, target: RasterizeType): + def rasterize(self, target: RasterizeType) -> None: self.app.rasterize(target) - def posterize(self, levels): + def posterize(self, levels: int) -> None: self.app.posterize(levels) - def move(self, relativeObject, insertionLocation): - self.app.move(relativeObject, insertionLocation) - - def merge(self): + def merge(self) -> "ArtLayer": return ArtLayer(self.app.merge()) - def link(self, with_layer): - self.app.link(with_layer) - - def unlink(self): - """Unlink this layer from any linked layers.""" - self.app.unlink() - - def invert(self): + def invert(self) -> None: self.app.invert() - def duplicate(self, relativeObject=None, insertionLocation=None): + def duplicate( + self, + relativeObject: "Layer | None" = None, + insertionLocation: ElementPlacement | None = None, + ) -> "ArtLayer": """Duplicates the layer. Args: @@ -549,10 +479,9 @@ def duplicate(self, relativeObject=None, insertionLocation=None): ArtLayer: The duplicated layer. """ - dup = self.app.duplicate(relativeObject, insertionLocation) - return ArtLayer(dup) + return ArtLayer(self.app.duplicate(relativeObject, insertionLocation)) - def convertToSmartObject(self): + def convertToSmartObject(self) -> "ArtLayer": """Converts the layer to a smart object. Returns: diff --git a/photoshop/api/_artlayers.py b/photoshop/api/_artlayers.py index dddb7fa7..6fa08d32 100644 --- a/photoshop/api/_artlayers.py +++ b/photoshop/api/_artlayers.py @@ -1,72 +1,20 @@ -# Import third-party modules -from comtypes import ArgumentError - # Import local modules from photoshop.api._artlayer import ArtLayer from photoshop.api._core import Photoshop -from photoshop.api.errors import PhotoshopPythonAPIError +from photoshop.api.collections import ( + CollectionOfNamedObjects, + CollectionOfRemovables, + CollectionWithAdd, +) # pylint: disable=too-many-public-methods -class ArtLayers(Photoshop): +class ArtLayers( + CollectionOfRemovables[ArtLayer, int | str], + CollectionOfNamedObjects[ArtLayer, int | str], + CollectionWithAdd[ArtLayer, int | str], +): """The collection of art layer objects in the document.""" - def __init__(self, parent): - super().__init__(parent=parent) - self._flag_as_method( - "add", - ) - - @property - def _layers(self): - return list(self.app) - - def __len__(self): - return self.length - - def __iter__(self): - for layer in self.app: - yield layer - - def __getitem__(self, key: str): - """Access a given ArtLayer using dictionary key lookup.""" - try: - return ArtLayer(self.app[key]) - except ArgumentError: - raise PhotoshopPythonAPIError(f'Could not find an artLayer named "{key}"') - - @property - def length(self): - return len(self._layers) - - @property - def parent(self): - return self.app.parent - - @property - def typename(self): - return self.app.typename - - def add(self): - """Adds an element.""" - return ArtLayer(self.app.add()) - - def getByIndex(self, index: int): - """Access ArtLayer using list index lookup.""" - return ArtLayer(self._layers[index]) - - def getByName(self, name: str) -> ArtLayer: - """Get the first element in the collection with the provided name. - - Raises: - PhotoshopPythonAPIError: Could not find a artLayer. - """ - for layer in self.app: - if layer.name == name: - return ArtLayer(layer) - raise PhotoshopPythonAPIError(f'Could not find an artLayer named "{name}"') - - def removeAll(self): - """Deletes all elements.""" - for layer in self.app: - ArtLayer(layer).remove() + def __init__(self, parent: Photoshop | None = None) -> None: + super().__init__(ArtLayer, parent) diff --git a/photoshop/api/_channel.py b/photoshop/api/_channel.py index ee47309c..c95b3e2d 100644 --- a/photoshop/api/_channel.py +++ b/photoshop/api/_channel.py @@ -1,10 +1,17 @@ # Import local modules +from typing import TYPE_CHECKING + from photoshop.api._core import Photoshop +from photoshop.api.enumerations import ChannelType +from photoshop.api.solid_color import SolidColor + +if TYPE_CHECKING: + from photoshop.api._document import Document # pylint: disable=too-many-public-methods class Channel(Photoshop): - def __init__(self, parent): + def __init__(self, parent: Photoshop | None = None) -> None: super().__init__(parent=parent) self._flag_as_method( "duplicate", @@ -12,55 +19,56 @@ def __init__(self, parent): ) @property - def color(self): - return self.app.color + def color(self) -> SolidColor: + return SolidColor(self.app.color) @color.setter - def color(self, value): + def color(self, value: SolidColor) -> None: self.app.color = value @property - def histogram(self): + def histogram(self) -> tuple[int, ...]: return self.app.histogram @histogram.setter - def histogram(self, value): + def histogram(self, value: tuple[int, ...]) -> None: self.app.histogram = value @property - def kind(self): - return self.app.kind + def kind(self) -> ChannelType: + return ChannelType(self.app.kind) @kind.setter - def kind(self, value): + def kind(self, value: ChannelType) -> None: self.app.kind = value @property - def opacity(self): + def opacity(self) -> float: return self.app.opacity @opacity.setter - def opacity(self, value): + def opacity(self, value: float) -> None: self.app.opacity = value @property - def visible(self): + def visible(self) -> bool: return self.app.visible @visible.setter - def visible(self, value): + def visible(self, value: bool) -> None: self.app.visible = value @property - def name(self): + def name(self) -> str: return self.app.name - def duplicate(self, targetDocument=None): - self.app.duplicate(targetDocument) + def duplicate(self, targetDocument: "Document | None" = None) -> "Channel": + return Channel(self.app.duplicate(targetDocument)) - def merge(self): + def merge(self) -> None: self.app.merge() - def remove(self): - channel = f'app.activeDocument.channels.getByName("{self.name}")' - self.eval_javascript(f"{channel}.remove()") + def remove(self) -> None: + self.eval_javascript( + f'app.activeDocument.channels.getByName("{self.name}").remove()' + ) diff --git a/photoshop/api/_channels.py b/photoshop/api/_channels.py index 00b3b6a2..df5d213b 100644 --- a/photoshop/api/_channels.py +++ b/photoshop/api/_channels.py @@ -1,44 +1,17 @@ # Import local modules from photoshop.api._channel import Channel from photoshop.api._core import Photoshop -from photoshop.api.errors import PhotoshopPythonAPIError - - -# pylint: disable=too-many-public-methods -class Channels(Photoshop): - def __init__(self, parent): - super().__init__(parent=parent) - self._flag_as_method( - "add", - "removeAll", - ) - - @property - def _channels(self): - return list(self.app) - - def __len__(self): - return self.length - - def __iter__(self): - for layer in self.app: - yield layer - - def __getitem__(self, item): - return self.app[item] - - @property - def length(self): - return len(self._channels) - - def add(self): - self.app.add() - - def removeAll(self): - self.app.removeAll() - - def getByName(self, name) -> Channel: - for channel in self._channels: - if channel.name == name: - return Channel(channel) - raise PhotoshopPythonAPIError(f'Could not find a channel named "{name}"') +from photoshop.api.collections import ( + CollectionOfNamedObjects, + CollectionOfRemovables, + CollectionWithAdd, +) + + +class Channels( + CollectionWithAdd[Channel, int | str], + CollectionOfRemovables[Channel, int | str], + CollectionOfNamedObjects[Channel, int | str], +): + def __init__(self, parent: Photoshop | None = None) -> None: + super().__init__(Channel, parent=parent) diff --git a/photoshop/api/_core.py b/photoshop/api/_core.py index 28be680a..cd11fc4e 100644 --- a/photoshop/api/_core.py +++ b/photoshop/api/_core.py @@ -1,17 +1,13 @@ """This class provides all photoshop API core functions.""" + # Import built-in modules -from contextlib import suppress -from functools import cached_property -from logging import CRITICAL -from logging import DEBUG -from logging import Logger -from logging import getLogger import os import platform -from typing import Any -from typing import List -from typing import Optional import winreg +from contextlib import suppress +from functools import cached_property +from logging import CRITICAL, DEBUG, Logger, getLogger +from typing import TYPE_CHECKING, Any # Import third-party modules from comtypes.client import CreateObject @@ -30,7 +26,9 @@ class Photoshop: _reg_path = "SOFTWARE\\Adobe\\Photoshop" object_name: str = "Application" - def __init__(self, ps_version: Optional[str] = None, parent: Any = None): + def __init__( + self, ps_version: str | None = None, parent: "Photoshop | None" = None + ): """ Initialize the Photoshop core object. @@ -40,7 +38,9 @@ def __init__(self, ps_version: Optional[str] = None, parent: Any = None): """ # Establish the initial app and program ID ps_version = os.getenv("PS_VERSION", ps_version) - self._app_id = PHOTOSHOP_VERSION_MAPPINGS.get(ps_version, "") + self._app_id = ( + PHOTOSHOP_VERSION_MAPPINGS.get(ps_version, "") if ps_version else "" + ) self._has_parent, self.adobe, self.app = False, None, None # Store current photoshop version @@ -49,7 +49,7 @@ def __init__(self, ps_version: Optional[str] = None, parent: Any = None): # Establish the application object using provided version ID if self.app_id: - self.app = self._get_application_object([self.app_id]) + self.app: Any = self._get_application_object([self.app_id]) if not self.app: # Attempt unsuccessful self._logger.debug( @@ -62,7 +62,9 @@ def __init__(self, ps_version: Optional[str] = None, parent: Any = None): self.app = self._get_application_object(versions) if not self.app: # All attempts exhausted - raise PhotoshopPythonAPIError("Please check if you have Photoshop installed correctly.") + raise PhotoshopPythonAPIError( + "Please check if you have Photoshop installed correctly." + ) # Add the parent app object if parent: @@ -70,20 +72,19 @@ def __init__(self, ps_version: Optional[str] = None, parent: Any = None): self.app = parent self._has_parent = True - def __repr__(self): - return self - - def __call__(self, *args, **kwargs): + def __call__(self): return self.app - def __str__(self): + def __str__(self) -> str: return f"{self.__class__.__name__} <{self.program_name}>" - def __getattribute__(self, item): - try: - return super().__getattribute__(item) - except AttributeError: - return getattr(self.app, item) + if not TYPE_CHECKING: + + def __getattribute__(self, name): + try: + return super().__getattribute__(name) + except AttributeError: + return getattr(self.app, name) """ * Debug Logger @@ -92,7 +93,9 @@ def __getattribute__(self, item): @cached_property def _debug(self) -> bool: """bool: Enable DEBUG level in logger if PS_DEBUG environment variable is truthy.""" - return bool(os.getenv("PS_DEBUG", "False").lower() in ["y", "t", "on", "yes", "true"]) + return bool( + os.getenv("PS_DEBUG", "False").lower() in ["y", "t", "on", "yes", "true"] + ) @cached_property def _logger(self) -> Logger: @@ -141,14 +144,14 @@ def app_id(self) -> str: return self._app_id @app_id.setter - def app_id(self, value: str): + def app_id(self, value: str) -> None: self._app_id = value """ * Private Methods """ - def _flag_as_method(self, *names: str): + def _flag_as_method(self, *names: str) -> None: """ * This is a hack for Photoshop's broken COM implementation. * Photoshop does not implement 'IDispatch::GetTypeInfo', so when @@ -161,7 +164,7 @@ def _flag_as_method(self, *names: str): if isinstance(self.app, FullyDynamicDispatch): self.app._FlagAsMethod(*names) - def _get_photoshop_versions(self) -> List[str]: + def _get_photoshop_versions(self) -> list[str]: """Retrieve a list of Photoshop version ID's from registry.""" with suppress(OSError, IndexError): key = self._open_key(self._reg_path) @@ -169,10 +172,14 @@ def _get_photoshop_versions(self) -> List[str]: versions = [winreg.EnumKey(key, i).split(".")[0] for i in range(key_count)] # Sort from latest version to oldest, use blank version as a fallback return [*sorted(versions, reverse=True), ""] - self._logger.debug("Unable to find Photoshop version number in HKEY_LOCAL_MACHINE registry!") + self._logger.debug( + "Unable to find Photoshop version number in HKEY_LOCAL_MACHINE registry!" + ) return [] - def _get_application_object(self, versions: List[str] = None) -> Optional[Dispatch]: + def _get_application_object( + self, versions: list[str] | None = None + ) -> Dispatch | None: """ Try each version string until a valid Photoshop application Dispatch object is returned. @@ -185,10 +192,11 @@ def _get_application_object(self, versions: List[str] = None) -> Optional[Dispat Raises: OSError: If a Dispatch object wasn't resolved. """ - for v in versions: - self.app_id = v - with suppress(OSError): - return CreateObject(self.program_name, dynamic=True) + if versions: + for v in versions: + self.app_id = v + with suppress(OSError): + return CreateObject(self.program_name, dynamic=True) return """ @@ -212,10 +220,16 @@ def get_script_path(self) -> str: """str: The absolute scripts path of Photoshop.""" return os.path.join(self.presets_path, "Scripts") - def eval_javascript(self, javascript: str, Arguments: Any = None, ExecutionMode: Any = None) -> str: + def eval_javascript( + self, javascript: str, Arguments: Any = None, ExecutionMode: Any = None + ) -> str: """Instruct the application to execute javascript code.""" executor = self.adobe if self._has_parent else self.app - return executor.doJavaScript(javascript, Arguments, ExecutionMode) + if executor: + return executor.doJavaScript(javascript, Arguments, ExecutionMode) + else: + print("Tried to eval javascript, but executor is not available.") + return "" """ * Private Static Methods @@ -238,9 +252,13 @@ def _open_key(key: str) -> winreg.HKEYType: mappings = {"AMD64": winreg.KEY_WOW64_64KEY} access = winreg.KEY_READ | mappings.get(machine_type, winreg.KEY_WOW64_32KEY) try: - return winreg.OpenKey(key=winreg.HKEY_LOCAL_MACHINE, sub_key=key, access=access) + return winreg.OpenKey( + key=winreg.HKEY_LOCAL_MACHINE, sub_key=key, access=access + ) except FileNotFoundError as err: raise OSError( "Failed to read the registration: <{path}>\n" - "Please check if you have Photoshop installed correctly.".format(path=f"HKEY_LOCAL_MACHINE\\{key}") + "Please check if you have Photoshop installed correctly.".format( + path=f"HKEY_LOCAL_MACHINE\\{key}" + ) ) from err diff --git a/photoshop/api/_document.py b/photoshop/api/_document.py index ff8cd36c..370e5066 100644 --- a/photoshop/api/_document.py +++ b/photoshop/api/_document.py @@ -14,12 +14,9 @@ """ # Import built-in modules +from os import PathLike from pathlib import Path -from typing import List -from typing import NoReturn -from typing import Optional -from typing import TypeVar -from typing import Union +from typing import TYPE_CHECKING, Optional # Import third-party modules from comtypes import COMError @@ -27,23 +24,46 @@ # Import local modules from photoshop.api._artlayer import ArtLayer from photoshop.api._artlayers import ArtLayers +from photoshop.api._channel import Channel from photoshop.api._channels import Channels from photoshop.api._core import Photoshop from photoshop.api._documentinfo import DocumentInfo +from photoshop.api._layer import Layer from photoshop.api._layerComps import LayerComps +from photoshop.api._layers import Layers from photoshop.api._layerSet import LayerSet from photoshop.api._layerSets import LayerSets -from photoshop.api._layers import Layers -from photoshop.api._selection import Selection -from photoshop.api.enumerations import ExportType -from photoshop.api.enumerations import ExtensionType -from photoshop.api.enumerations import SaveOptions -from photoshop.api.enumerations import TrimType +from photoshop.api.enumerations import ( + AnchorPosition, + BitsPerChannelType, + ChangeMode, + ColorProfileType, + Direction, + DocumentMode, + ExportType, + ExtensionType, + Intent, + MeasurementSource, + ResampleMethod, + SaveOptions, + SourceSpaceType, + TrimType, +) +from photoshop.api.path_items import PathItems +from photoshop.api.protocols import HistoryState, MeasurementScale, XMPMetadata from photoshop.api.save_options import ExportOptionsSaveForWeb +from photoshop.api.save_options.bmp import BMPSaveOptions +from photoshop.api.save_options.eps import EPSSaveOptions +from photoshop.api.save_options.gif import GIFSaveOptions +from photoshop.api.save_options.jpg import JPEGSaveOptions +from photoshop.api.save_options.pdf import PDFSaveOptions +from photoshop.api.save_options.png import PNGSaveOptions +from photoshop.api.save_options.psd import PhotoshopSaveOptions +from photoshop.api.save_options.tag import TargaSaveOptions +from photoshop.api.save_options.tif import TiffSaveOptions - -# Custom types. -PS_Layer = TypeVar("PS_Layer", LayerSet, ArtLayer) +if TYPE_CHECKING: + from photoshop.api._selection import Selection # pylint: disable=too-many-public-methods @@ -56,7 +76,7 @@ class Document(Photoshop): object_name = "Application" - def __init__(self, parent): + def __init__(self, parent: Photoshop | None = None) -> None: super().__init__(parent=parent) self._flag_as_method( "autoCount", @@ -64,6 +84,7 @@ def __init__(self, parent): "close", "convertProfile", "Flatten", + "flipCanvas", "mergeVisibleLayers", "crop", "export", @@ -77,7 +98,9 @@ def __init__(self, parent): "splitChannels", "trap", "trim", + "resizeCanvas", "resizeImage", + "rotateCanvas", ) @property @@ -85,15 +108,18 @@ def artLayers(self) -> ArtLayers: return ArtLayers(self.app.artLayers) @property - def activeLayer(self) -> PS_Layer: + def activeLayer(self) -> ArtLayer | LayerSet: """The selected layer.""" type_ = self.eval_javascript("app.activeDocument.activeLayer.typename") - mappings = {"LayerSet": LayerSet, "ArtLayer": ArtLayer} + mappings: dict[str, type[ArtLayer] | type[LayerSet]] = { + "LayerSet": LayerSet, + "ArtLayer": ArtLayer, + } func = mappings[type_] return func(self.app.activeLayer) @activeLayer.setter - def activeLayer(self, layer) -> NoReturn: + def activeLayer(self, layer: Layer) -> None: """Sets the select layer as active layer. Args: @@ -104,64 +130,64 @@ def activeLayer(self, layer) -> NoReturn: self.app.activeLayer = layer @property - def activeChannels(self): + def activeChannels(self) -> list[Channel]: """The selected channels.""" - return self.app.activeChannels + return [Channel(channel) for channel in self.app.activeChannels] @activeChannels.setter - def activeChannels(self, channels): + def activeChannels(self, channels: list[Channel]) -> None: self.app.activeChannels = channels @property - def activeHistoryBrushSource(self): + def activeHistoryBrushSource(self) -> HistoryState: """The history state to use with the history brush.""" return self.app.activeHistoryBrushSource @property - def activeHistoryState(self): + def activeHistoryState(self) -> HistoryState: """The current history state for this document.""" return self.app.activeHistoryState @activeHistoryState.setter - def activeHistoryState(self, state): + def activeHistoryState(self, state: HistoryState) -> None: self.app.activeHistoryState = state @property - def backgroundLayer(self): + def backgroundLayer(self) -> ArtLayer: """The background layer for the Document.""" - return self.app.backgroundLayer + return ArtLayer(self.app.backgroundLayer) @property - def bitsPerChannel(self): + def bitsPerChannel(self) -> BitsPerChannelType: """The number of bits per channel.""" - return self.app.bitsPerChannel + return BitsPerChannelType(self.app.bitsPerChannel) @bitsPerChannel.setter - def bitsPerChannel(self, value): + def bitsPerChannel(self, value: BitsPerChannelType) -> None: self.app.bitsPerChannel = value @property - def channels(self): + def channels(self) -> Channels: return Channels(self.app.channels) @property - def colorProfileName(self): + def colorProfileName(self) -> str: """The name of the color profile. Valid only when no value is specified for color profile kind (to indicate a custom color profile).""" return self.app.colorProfileName @colorProfileName.setter - def colorProfileName(self, name): + def colorProfileName(self, name: str) -> None: self.app.colorProfileName = name @property - def colorProfileType(self): + def colorProfileType(self) -> ColorProfileType: """The type of color model that defines the working space of the Document.""" - return self.app.colorProfileType + return ColorProfileType(self.app.colorProfileType) @colorProfileType.setter - def colorProfileType(self, profile_type): + def colorProfileType(self, profile_type: ColorProfileType) -> None: self.app.colorProfileType = profile_type @property @@ -170,9 +196,9 @@ def colorSamplers(self): return self.app.colorSamplers @property - def componentChannels(self): + def componentChannels(self) -> list[Channel]: """The color component channels for this Document.""" - return self.app.componentChannels + return [Channel(channel) for channel in self.app.componentChannels] @property def countItems(self): @@ -180,22 +206,22 @@ def countItems(self): return self.app.countItems @property - def fullName(self): + def fullName(self) -> Path | None: """The full path name of the Document.""" try: return Path(self.app.fullName) except COMError: self.eval_javascript( - 'alert ("Please save your Document first!",' '"{}")'.format(self.name), + 'alert ("Please save your Document first!","{}")'.format(self.name), ) @property - def height(self): + def height(self) -> float: """The height of the Document.""" return self.app.Height @property - def histogram(self): + def histogram(self) -> tuple[int, ...]: """A histogram showing the number of pixels at each color intensity level for the composite channel.""" return self.app.Histogram @@ -206,44 +232,44 @@ def history_states(self): return self.app.HistoryStates @property - def id(self): + def id(self) -> int: """The unique ID of this Document.""" return self.app.Id @property - def info(self): + def info(self) -> DocumentInfo: """Metadata about the Document.""" return DocumentInfo(self.app.info) @property - def layerComps(self): + def layerComps(self) -> LayerComps: """The layer comps collection in this Document.""" return LayerComps(self.app.layerComps) @property - def layers(self): + def layers(self) -> Layers: """The layers collection in the Document.""" return Layers(self.app.Layers) @property - def layerSets(self): + def layerSets(self) -> LayerSets: """The layer sets collection in the Document.""" return LayerSets(self.app.layerSets) @property - def managed(self): + def managed(self) -> bool: """If true, the Document is a workgroup Document.""" return self.app.Managed @property - def measurement_scale(self): + def measurement_scale(self) -> MeasurementScale: """The measurement scale of the Document.""" return self.app.MeasurementScale @property - def mode(self): + def mode(self) -> DocumentMode: """The color profile.""" - return self.app.Mode + return DocumentMode(self.app.Mode) @property def name(self) -> str: @@ -251,30 +277,30 @@ def name(self) -> str: return self.app.name @property - def parent(self): + def parent(self) -> object: """The object's container.""" return self.app.Parent @property - def path(self) -> str: + def path(self) -> Path | None: """The path to the Document.""" try: return Path(self.app.path) except COMError: self.eval_javascript( - 'alert ("Please save your Document first!",' '"{}")'.format(self.name), + 'alert ("Please save your Document first!","{}")'.format(self.name), ) @path.setter - def path(self, path: str) -> NoReturn: - self.app.fullName = path + def path(self, path: str | PathLike[str]) -> None: + self.app.fullName = str(path) @property - def pathItems(self): - return self.app.pathItems + def pathItems(self) -> PathItems: + return PathItems(self.app.pathItems) @property - def pixelAspectRatio(self): + def pixelAspectRatio(self) -> float: """The (custom) pixel aspect ratio of the Document. Range: 0.100 to 10.000. @@ -288,29 +314,26 @@ def printSettings(self): return self.app.printSettings @property - def quickMaskMode(self): + def quickMaskMode(self) -> bool: """If true, the document is in Quick Mask mode.""" return self.app.quickMaskMode @property - def saved(self): + def saved(self) -> bool: """If true, the Document been saved since the last change.""" return self.app.Saved @property - def resolution(self): + def resolution(self) -> float: """The resolution of the Document (in pixels per inch)""" return self.app.resolution @property - def selection(self): + def selection(self) -> "Selection": """The selected area of the Document.""" - return Selection(self.app.selection) + from ._selection import Selection - @property - def typename(self): - """The class name of the object.""" - return self.app.typename + return Selection(self.app.selection) @property def cloudDocument(self): @@ -323,45 +346,56 @@ def cloudWorkAreaDirectory(self): return self.app.cloudWorkAreaDirectory @property - def width(self): + def width(self) -> float: return self.app.Width @property - def xmpMetadata(self): + def xmpMetadata(self) -> XMPMetadata: """The XMP properties of the Document. The Camera RAW settings are stored here.""" return self.app.xmpMetadata # Methods - def autoCount(self, *args, **kwargs): + def autoCount(self, channel: Channel, threshold: int) -> None: """Counts the objects in the Document.""" - return self.app.autoCount(*args, **kwargs) + self.app.autoCount(channel, threshold) - def changeMode(self, *args, **kwargs): + def changeMode(self, destinationMode: ChangeMode, options: object) -> None: """Changes the mode of the Document.""" - return self.app.changeMode(*args, **kwargs) + self.app.changeMode(destinationMode, options) - def close(self, saving=SaveOptions.DoNotSaveChanges): - return self.app.close(saving) + def close(self, saving: SaveOptions = SaveOptions.DoNotSaveChanges) -> None: + self.app.close(saving) - def convertProfile(self): - return self.app.convertProfile() + def convertProfile( + self, + destinationProfile: str, + intent: Intent, + blackPointCompensation: bool, + dither: bool, + ) -> None: + self.app.convertProfile( + destinationProfile, intent, blackPointCompensation, dither + ) - def flatten(self): + def flatten(self) -> None: """Flattens all layers.""" - return self.app.Flatten() + self.app.Flatten() + + def flipCanvas(self, direction: Direction) -> None: + self.app.flipCanvas(direction) - def mergeVisibleLayers(self): + def mergeVisibleLayers(self) -> None: """Flattens all visible layers in the Document.""" - return self.app.mergeVisibleLayers() + self.app.mergeVisibleLayers() def crop( self, - bounds: List[int], - angle: Optional[float] = None, - width: Optional[int] = None, - height: Optional[int] = None, - ): + bounds: list[int], + angle: float | None = None, + width: int | None = None, + height: int | None = None, + ) -> None: """Crops the document. Args: @@ -371,9 +405,14 @@ def crop( height: The height of the resulting document. """ - return self.app.crop(bounds, angle, width, height) + self.app.crop(bounds, angle, width, height) - def exportDocument(self, file_path: str, exportAs: ExportType, options: Union[ExportOptionsSaveForWeb]): + def exportDocument( + self, + file_path: str, + exportAs: ExportType, + options: ExportOptionsSaveForWeb, + ) -> None: """Exports the Document. Note: @@ -387,59 +426,84 @@ def exportDocument(self, file_path: str, exportAs: ExportType, options: Union[Ex file_path = file_path.replace("\\", "/") self.app.export(file_path, exportAs, options) - def duplicate(self, name=None, merge_layers_only=False): + def duplicate( + self, name: str | None = None, merge_layers_only: bool = False + ) -> "Document": return Document(self.app.duplicate(name, merge_layers_only)) - def paste(self): + def paste(self) -> ArtLayer | LayerSet: """Pastes contents of the clipboard into the Document.""" self.eval_javascript("app.activeDocument.paste()") return self.activeLayer - def print(self): + def print( + self, + sourceSpace: SourceSpaceType, + printSpace: str, + intent: Intent, + blackPointCompensation: bool, + ) -> None: """Prints the document.""" - return self.app.print() + self.app.print(sourceSpace, printSpace, intent, blackPointCompensation) - def printOneCopy(self): + def printOneCopy(self) -> None: self.app.printOneCopy() - def rasterizeAllLayers(self): - return self.app.rasterizeAllLayers() + def rasterizeAllLayers(self) -> None: + self.app.rasterizeAllLayers() - def recordMeasurements(self, source, dataPoints): + def recordMeasurements(self, source: MeasurementSource, dataPoints: str) -> None: """Records the measurements of document.""" self.app.recordMeasurements(source, dataPoints) - def reveal_all(self): + def reveal_all(self) -> None: """Expands the Document to show clipped sections.""" - return self.app.revealAll() + self.app.revealAll() - def save(self): + def save(self) -> None: """Saves the Document.""" - return self.app.save() + self.app.save() - def saveAs(self, file_path, options, asCopy=True, extensionType=ExtensionType.Lowercase): + def saveAs( + self, + file_path: str, + options: BMPSaveOptions + | EPSSaveOptions + | GIFSaveOptions + | JPEGSaveOptions + | PDFSaveOptions + | PNGSaveOptions + | PhotoshopSaveOptions + | TargaSaveOptions + | TiffSaveOptions + | None = None, + asCopy: bool = False, + extensionType: ExtensionType = ExtensionType.Lowercase, + ) -> None: """Saves the documents with the specified save options. Args: - file_path (str): Absolute path of psd file. - options (JPEGSaveOptions): Save options. - asCopy (bool): + file_path: Absolute path of psd file. + options: Save options. + asCopy: Saves the document as a copy, leaving the original open. """ - return self.app.saveAs(file_path, options, asCopy, extensionType) + self.app.saveAs(file_path, options, asCopy, extensionType) - def splitChannels(self): + def splitChannels(self) -> None: """Splits the channels of the document.""" self.app.splitChannels() - def suspendHistory(self, historyString, javaScriptString): + def suspendHistory(self, historyString: str, javaScriptString: str) -> None: """Provides a single history state for the entire script. Allows a single undo for all actions taken in the script. """ - self.eval_javascript(f"app.activeDocument.suspendHistory('{historyString}', '{javaScriptString}')") + self.eval_javascript( + f"app.activeDocument.suspendHistory('{historyString}', '{javaScriptString}')" + ) - def trap(self, width: int): + def trap(self, width: int) -> None: """ Applies trapping to a CMYK document. Valid only when ‘mode’ = CMYK. @@ -454,7 +518,7 @@ def trim( left: Optional[bool] = True, bottom: Optional[bool] = True, right: Optional[bool] = True, - ): + ) -> None: """Trims the transparent area around the image on the specified sides of the canvas. Args: @@ -471,16 +535,35 @@ def trim( right: If true, trims away the right of the document. """ - return self.app.trim(trim_type, top, left, bottom, right) + self.app.trim(trim_type, top, left, bottom, right) - def resizeImage(self, width: int, height: int, resolution: int = 72, automatic: int = 8): + def resizeCanvas( + self, + width: int, + height: int, + anchor: AnchorPosition = AnchorPosition.MiddleCenter, + ) -> None: + self.app.resizeCanvas(width, height, anchor) + + def resizeImage( + self, + width: int, + height: int, + resolution: float = 72, + resampleMethod: ResampleMethod = ResampleMethod.Automatic, + amount: int = 0, + ) -> None: """Changes the size of the image. Args: width: The desired width of the image. height: The desired height of the image. resolution: The resolution (in pixels per inch) - automatic: Value for automatic. + resampleMethod: The downsample method. + amount: Amount of noise value when using preserve details (range: 0 - 100) """ - return self.app.resizeImage(width, height, resolution, automatic) + self.app.resizeImage(width, height, resolution, resampleMethod, amount) + + def rotateCanvas(self, angle: float) -> None: + self.app.rotateCanvas(angle) diff --git a/photoshop/api/_documentinfo.py b/photoshop/api/_documentinfo.py index c24068cd..5c38729b 100644 --- a/photoshop/api/_documentinfo.py +++ b/photoshop/api/_documentinfo.py @@ -7,19 +7,21 @@ # Import built-in modules from pprint import pformat +from typing import Sequence # Import local modules from photoshop.api._core import Photoshop +from photoshop.api.enumerations import CopyrightedType, Urgency # pylint: disable=too-many-public-methods class DocumentInfo(Photoshop): """Metadata about a document object.""" - def __init__(self, parent): + def __init__(self, parent: Photoshop | None = None) -> None: super().__init__(parent=parent) - def __str__(self): + def __str__(self) -> str: return pformat( { "author": self.author, @@ -49,195 +51,195 @@ def __str__(self): ) @property - def author(self): + def author(self) -> str: """str: The author.""" return self.app.author @author.setter - def author(self, name): + def author(self, name: str) -> None: self.app.author = name @property - def authorPosition(self): + def authorPosition(self) -> str: """str:The author’s position.""" return self.app.authorPosition @authorPosition.setter - def authorPosition(self, name): + def authorPosition(self, name: str) -> None: self.app.authorPosition = name @property - def caption(self): + def caption(self) -> str: return self.app.caption @caption.setter - def caption(self, name): + def caption(self, name: str) -> None: self.app.caption = name @property - def captionWriter(self): + def captionWriter(self) -> str: return self.app.captionWriter @captionWriter.setter - def captionWriter(self, name): + def captionWriter(self, name: str) -> None: self.app.captionWriter = name @property - def category(self): + def category(self) -> str: """str: The document category.""" return self.app.category @category.setter - def category(self, name): + def category(self, name: str) -> None: self.app.category = name @property - def city(self): + def city(self) -> str: return self.app.city @city.setter - def city(self, city_name): + def city(self, city_name: str) -> None: self.app.city = city_name @property - def copyrightNotice(self): + def copyrightNotice(self) -> str: """str: The copyright notice.""" return self.app.copyrightNotice @copyrightNotice.setter - def copyrightNotice(self, name): + def copyrightNotice(self, name: str) -> None: self.app.copyrightNotice = name @property - def copyrighted(self): - """str: The copyright status.""" - return self.app.copyrighted + def copyrighted(self) -> CopyrightedType: + """The copyright status.""" + return CopyrightedType(self.app.copyrighted) @copyrighted.setter - def copyrighted(self, info): + def copyrighted(self, info: CopyrightedType) -> None: self.app.copyrighted = info @property - def country(self): + def country(self) -> str: return self.app.country @country.setter - def country(self, name): + def country(self, name: str) -> None: self.app.country = name @property - def creationDate(self): + def creationDate(self) -> str: return self.app.creationDate @creationDate.setter - def creationDate(self, name): + def creationDate(self, name: str) -> None: self.app.creationDate = name @property - def credit(self): + def credit(self) -> str: """str: The author credit.""" return self.app.credit @credit.setter - def credit(self, value): + def credit(self, value: str) -> None: self.app.credit = value @property - def exif(self): + def exif(self) -> tuple[tuple[str, str], ...]: return self.app.exif @exif.setter - def exif(self, info): + def exif(self, info: Sequence[tuple[str, str]]) -> None: self.app.exif = info @property - def headline(self): + def headline(self) -> str: return self.app.headline @headline.setter - def headline(self, value): + def headline(self, value: str) -> None: self.app.headline = value @property - def instructions(self): + def instructions(self) -> str: return self.app.instructions @instructions.setter - def instructions(self, value): + def instructions(self, value: str) -> None: self.app.instructions = value @property - def jobName(self): + def jobName(self) -> str: return self.app.jobName @jobName.setter - def jobName(self, job): + def jobName(self, job: str) -> None: self.app.jobName = job @property - def keywords(self): + def keywords(self) -> tuple[str, ...] | None: return self.app.keywords @keywords.setter - def keywords(self, words): + def keywords(self, words: Sequence[str]) -> None: self.app.keywords = words @property - def ownerUrl(self): + def ownerUrl(self) -> str: return self.app.ownerUrl @ownerUrl.setter - def ownerUrl(self, url): + def ownerUrl(self, url: str) -> None: self.app.ownerUrl = url @property - def provinceState(self): + def provinceState(self) -> str: """str: The state or province.""" return self.app.provinceState @provinceState.setter - def provinceState(self, state_name): + def provinceState(self, state_name: str) -> None: self.app.provinceState = state_name @property - def source(self): + def source(self) -> str: return self.app.source @source.setter - def source(self, source_name): + def source(self, source_name: str) -> None: self.app.source = source_name @property - def supplementalCategories(self): + def supplementalCategories(self) -> tuple[str, ...]: """str: Other categories.""" return self.app.supplementalCategories @supplementalCategories.setter - def supplementalCategories(self, info): + def supplementalCategories(self, info: Sequence[str]) -> None: self.app.supplementalCategories = info @property - def title(self): + def title(self) -> str: return self.app.title @title.setter - def title(self, name): + def title(self, name: str) -> None: self.app.title = name @property - def transmissionReference(self): + def transmissionReference(self) -> str: """str: The transmission reference.""" return self.app.transmissionReference @transmissionReference.setter - def transmissionReference(self, reference): + def transmissionReference(self, reference: str) -> None: self.app.transmissionReference = reference @property - def urgency(self): + def urgency(self) -> Urgency: """The document urgency.""" - return self.app.urgency + return Urgency(self.app.urgency) @urgency.setter - def urgency(self, status): + def urgency(self, status: Urgency) -> None: self.app.urgency = status diff --git a/photoshop/api/_documents.py b/photoshop/api/_documents.py index 365aaefc..09718b8e 100644 --- a/photoshop/api/_documents.py +++ b/photoshop/api/_documents.py @@ -1,34 +1,29 @@ # Import local modules from photoshop.api._core import Photoshop from photoshop.api._document import Document -from photoshop.api.enumerations import BitsPerChannelType -from photoshop.api.enumerations import DocumentFill -from photoshop.api.enumerations import NewDocumentMode -from photoshop.api.errors import PhotoshopPythonAPIError +from photoshop.api.collections import CollectionOfNamedObjects +from photoshop.api.enumerations import BitsPerChannelType, DocumentFill, NewDocumentMode # pylint: disable=too-many-public-methods, too-many-arguments -class Documents(Photoshop): +class Documents(CollectionOfNamedObjects[Document, int | str]): """The collection of open documents.""" - def __init__(self, parent): - super().__init__(parent=parent) + def __init__(self, parent: Photoshop | None = None) -> None: + super().__init__(Document, parent) self._flag_as_method("add") - def __len__(self) -> int: - return self.length - def add( self, width: int = 960, height: int = 540, resolution: float = 72.0, - name: str = None, - mode: int = NewDocumentMode.NewRGB, - initialFill: int = DocumentFill.White, + name: str | None = None, + mode: NewDocumentMode = NewDocumentMode.NewRGB, + initialFill: DocumentFill = DocumentFill.White, pixelAspectRatio: float = 1.0, - bitsPerChannel: int = BitsPerChannelType.Document8Bits, - colorProfileName: str = None, + bitsPerChannel: BitsPerChannelType = BitsPerChannelType.Document8Bits, + colorProfileName: str | None = None, ) -> Document: """Creates a new document object and adds it to this collections. @@ -61,25 +56,3 @@ def add( colorProfileName, ) ) - - def __iter__(self) -> Document: - for doc in self.app: - self.adobe.activeDocument = doc - yield Document(doc) - - def __getitem__(self, item) -> Document: - try: - return Document(self.app[item]) - except IndexError: - raise PhotoshopPythonAPIError("Currently Photoshop did not find Documents.") - - @property - def length(self) -> int: - return len(list(self.app)) - - def getByName(self, document_name: str) -> Document: - """Get document by given document name.""" - for doc in self.app: - if doc.name == document_name: - return Document(doc) - raise PhotoshopPythonAPIError(f'Could not find a document named "{document_name}"') diff --git a/photoshop/api/_layer.py b/photoshop/api/_layer.py new file mode 100644 index 00000000..3ade37e3 --- /dev/null +++ b/photoshop/api/_layer.py @@ -0,0 +1,150 @@ +from typing import TYPE_CHECKING + +from photoshop.api._core import Photoshop +from photoshop.api.enumerations import AnchorPosition, BlendMode, ElementPlacement +from photoshop.api.protocols import XMPMetadata + +if TYPE_CHECKING: + from photoshop.api._document import Document + from photoshop.api._layerSet import LayerSet + + +class Layer(Photoshop): + def __init__(self, parent: Photoshop | None = None) -> None: + super().__init__(parent=parent) + self._flag_as_method( + "delete", + "duplicate", + "link", + "move", + "moveToEnd", + "resize", + "rotate", + "translate", + "unlink", + ) + + @property + def allLocked(self) -> bool: + return self.app.allLocked + + @allLocked.setter + def allLocked(self, value: bool) -> None: + self.app.allLocked = value + + @property + def blendMode(self) -> BlendMode: + return BlendMode(self.app.blendMode) + + @blendMode.setter + def blendMode(self, mode: BlendMode) -> None: + self.app.blendMode = mode + + @property + def bounds(self) -> tuple[float, float, float, float]: + """The bounding rectangle of the layer.""" + return self.app.bounds + + @property + def boundsNoEffects(self) -> tuple[float, float, float, float]: + """Bounding rectangle of the Layer not including effects.""" + return self.app.boundsNoEffects + + @property + def id(self) -> int: + return self.app.id + + @property + def itemIndex(self) -> int: + return self.app.itemIndex + + @property + def linkedLayers(self) -> list["Layer"]: + """Get all layers linked to this layer. + + Returns: + list: Layer objects""" + return [Layer(layer) for layer in self.app.linkedLayers] + + @property + def name(self) -> str: + return self.app.name + + @name.setter + def name(self, text: str) -> None: + self.app.name = text + + @property + def opacity(self) -> float: + """The layer's master opacity (as a percentage). Range: 0.0 to 100.0.""" + return round(self.app.opacity) + + @opacity.setter + def opacity(self, value: float) -> None: + self.app.opacity = value + + @property + def parent(self) -> "Document": + """The object's container.""" + from photoshop.api._document import Document + + return Document(self.app.parent) + + @property + def visible(self) -> bool: + return self.app.visible + + @visible.setter + def visible(self, value: bool) -> None: + self.app.visible = value + + @property + def xmpMetadata(self) -> XMPMetadata: + return self.app.xmpMetadata + + def duplicate( + self, + relativeObject: "Layer | None" = None, + insertionLocation: ElementPlacement | None = None, + ) -> "Layer": + """Duplicates the layer. + + Args: + relativeObject: Layer or LayerSet. + insertionLocation: The location to insert the layer. + + Returns: + Layer: The duplicated layer. + """ + return Layer(self.app.duplicate(relativeObject, insertionLocation)) + + def link(self, with_layer: "Layer") -> None: + self.app.link(with_layer) + + def move( + self, relativeObject: "Layer | LayerSet", insertionLocation: ElementPlacement + ) -> None: + self.app.move(relativeObject, insertionLocation) + + def moveToEnd(self, layer_set: "LayerSet") -> None: + self.app.moveToEnd(layer_set) + + def remove(self) -> None: + """Removes this layer from the document.""" + self.app.delete() + + def resize( + self, horizontal: float, vertical: float, anchor: AnchorPosition + ) -> None: + """Scales the object.""" + self.app.resize(horizontal, vertical, anchor) + + def rotate(self, angle: float, anchor: AnchorPosition) -> None: + return self.app.rotate(angle, anchor) + + def translate(self, deltaX: float, deltaY: float) -> None: + return self.app.translate(deltaX, deltaY) + + def unlink(self) -> None: + """Unlink this layer from any linked layers.""" + self.app.unlink() diff --git a/photoshop/api/_layerComp.py b/photoshop/api/_layerComp.py index 79e60049..87f66108 100644 --- a/photoshop/api/_layerComp.py +++ b/photoshop/api/_layerComp.py @@ -1,11 +1,16 @@ # Import local modules +from typing import TYPE_CHECKING + from photoshop.api._core import Photoshop +if TYPE_CHECKING: + from photoshop.api._document import Document + class LayerComp(Photoshop): """A snapshot of a state of the layers in a document (can be used to view different page layouts or compostions).""" - def __init__(self, parent): + def __init__(self, parent: Photoshop | None = None) -> None: super().__init__(parent=parent) self._flag_as_method( "apply", @@ -14,87 +19,82 @@ def __init__(self, parent): "resetfromComp", ) - def __len__(self): - return self.length - @property - def appearance(self): + def appearance(self) -> bool: return self.app.appearance @appearance.setter - def appearance(self, value): + def appearance(self, value: bool) -> None: self.app.appearance = value @property - def childLayerCompState(self): + def childLayerCompState(self) -> bool: return self.app.childLayerCompState @childLayerCompState.setter - def childLayerCompState(self, value): + def childLayerCompState(self, value: bool) -> None: self.app.childLayerCompState = value @property - def comment(self): + def comment(self) -> str: return self.app.comment @comment.setter - def comment(self, text): + def comment(self, text: str) -> None: self.app.comment = text @property - def name(self): + def name(self) -> str: return self.app.name @name.setter - def name(self, text): + def name(self, text: str) -> None: self.app.name = text @property - def parent(self): - return self.app.parent + def parent(self) -> "Document": + from ._document import Document + + return Document(self.app.parent) @property - def position(self): + def position(self) -> bool: return self.app.position @position.setter - def position(self, value): + def position(self, value: bool) -> None: self.app.position = value @property - def selected(self): + def selected(self) -> bool: """True if the layer comp is currently selected.""" return self.app.selected @selected.setter - def selected(self, value): + def selected(self, value: bool) -> None: self.app.selected = value @property - def typename(self): - return self.app.typename - - @property - def visibility(self): + def visibility(self) -> bool: """True to use layer visibility settings.""" return self.app.visibility @visibility.setter - def visibility(self, value): + def visibility(self, value: bool) -> None: self.app.visibility = value - def apply(self): + def apply(self) -> None: """Applies the layer comp to the document.""" self.app.apply() - def recapture(self): + def recapture(self) -> None: """Recaptures the current layer state(s) for this layer comp.""" self.app.recapture() - def remove(self): + def remove(self) -> None: """Deletes the layerComp object.""" self.app.remove() - def resetfromComp(self): + def resetfromComp(self) -> None: """Resets the layer comp state to thedocument state.""" self.app.resetfromComp() diff --git a/photoshop/api/_layerComps.py b/photoshop/api/_layerComps.py index e57dcccf..c65cf5d6 100644 --- a/photoshop/api/_layerComps.py +++ b/photoshop/api/_layerComps.py @@ -1,58 +1,33 @@ # Import local modules from photoshop.api._core import Photoshop from photoshop.api._layerComp import LayerComp -from photoshop.api.errors import PhotoshopPythonAPIError +from photoshop.api.collections import ( + CollectionOfNamedObjects, + CollectionOfRemovables, +) -class LayerComps(Photoshop): +class LayerComps( + CollectionOfRemovables[LayerComp, int | str], + CollectionOfNamedObjects[LayerComp, int | str], +): """The layer comps collection in this document.""" - def __init__(self, parent): - super().__init__(parent=parent) - self._flag_as_method( - "add", - "removeAll", - ) - - def __len__(self): - return self.length - - @property - def length(self): - return len(self._layers) - - @property - def _layers(self): - return list(self.app) - - @property - def parent(self): - return self.app.parent - - @property - def typename(self): - return self.app.typename + def __init__(self, parent: Photoshop | None = None) -> None: + super().__init__(LayerComp, parent=parent) + self._flag_as_method("add") def add( self, - name, - comment="No Comment.", - appearance=True, - position=True, - visibility=True, - childLayerCompStat=False, - ): - return LayerComp(self.app.add(name, comment, appearance, position, visibility, childLayerCompStat)) - - def getByName(self, name): - for layer in self._layers: - if layer.name == name: - return LayerComp(layer) - raise PhotoshopPythonAPIError(f'Could not find a layer named "{name}"') - - def removeAll(self): - self.app.removeAll() - - def __iter__(self): - for layer in self._layers: - yield LayerComp(layer) + name: str, + comment: str = "No Comment.", + appearance: bool = True, + position: bool = True, + visibility: bool = True, + childLayerCompStat: bool = False, + ) -> LayerComp: + return LayerComp( + self.app.add( + name, comment, appearance, position, visibility, childLayerCompStat + ) + ) diff --git a/photoshop/api/_layerSet.py b/photoshop/api/_layerSet.py index 3351da61..4b5d4916 100644 --- a/photoshop/api/_layerSet.py +++ b/photoshop/api/_layerSet.py @@ -1,144 +1,74 @@ # Import local modules +from typing import TYPE_CHECKING, Iterator + from photoshop.api._artlayer import ArtLayer from photoshop.api._artlayers import ArtLayers +from photoshop.api._channel import Channel +from photoshop.api._channels import Channels from photoshop.api._core import Photoshop -from photoshop.api._layers import Layers -from photoshop.api.enumerations import AnchorPosition -from photoshop.api.enumerations import BlendMode +from photoshop.api._layer import Layer +from photoshop.api.enumerations import ElementPlacement + +if TYPE_CHECKING: + from photoshop.api._layers import Layers + from photoshop.api._layerSets import LayerSets -class LayerSet(Photoshop): +class LayerSet(Layer): """A group of layer objects, which can include art layer objects and other (nested) layer set objects. A single command or set of commands manipulates all layers in a layer set object. """ - def __init__(self, parent): + def __init__(self, parent: Photoshop | None = None) -> None: super().__init__(parent=parent) self._flag_as_method( - "merge", - "duplicate", "add", - "delete", - "link", - "move", - "resize", - "rotate", - "translate", - "unlink", + "merge", ) @property - def allLocked(self): - return self.app.allLocked - - @allLocked.setter - def allLocked(self, value): - self.app.allLocked = value - - @property - def artLayers(self): + def artLayers(self) -> ArtLayers: return ArtLayers(self.app.artLayers) @property - def blendMode(self): - return BlendMode(self.app.blendMode) - - @property - def bounds(self): - """The bounding rectangle of the layer set.""" - return self.app.bounds - - @property - def enabledChannels(self): - return self.app.enabledChannels + def enabledChannels(self) -> Channels: + return Channels(self.app.enabledChannels) @enabledChannels.setter - def enabledChannels(self, value): + def enabledChannels(self, value: list[Channel] | Channels) -> None: self.app.enabledChannels = value @property - def layers(self): + def layers(self) -> "Layers": + # pylint: disable=import-outside-toplevel + from ._layers import Layers + return Layers(self.app.layers) @property - def layerSets(self): + def layerSets(self) -> "LayerSets": # pylint: disable=import-outside-toplevel from ._layerSets import LayerSets return LayerSets(self.app.layerSets) - @property - def linkedLayers(self): - """The layers linked to this layerSet object.""" - return self.app.linkedLayers or [] - - @property - def name(self) -> str: - return self.app.name - - @name.setter - def name(self, value): - """The name of this layer set.""" - self.app.name = value - - @property - def opacity(self): - """The master opacity of the set.""" - return round(self.app.opacity) - - @opacity.setter - def opacity(self, value): - self.app.opacity = value - - @property - def parent(self): - return self.app.parent - - @property - def visible(self): - return self.app.visible - - @visible.setter - def visible(self, value): - self.app.visible = value - - def duplicate(self, relativeObject=None, insertionLocation=None): + def duplicate( + self, + relativeObject: Layer | None = None, + insertionLocation: ElementPlacement | None = None, + ): return LayerSet(self.app.duplicate(relativeObject, insertionLocation)) - def link(self, with_layer): - self.app.link(with_layer) - - def add(self): + def add(self) -> "LayerSet": """Adds an element.""" - self.app.add() + return LayerSet(self.app.add()) def merge(self) -> ArtLayer: """Merges the layer set.""" return ArtLayer(self.app.merge()) - def move(self, relativeObject, insertionLocation): - self.app.move(relativeObject, insertionLocation) - - def remove(self): - """Remove this layer set from the document.""" - self.app.delete() - - def resize(self, horizontal=None, vertical=None, anchor: AnchorPosition = None): - self.app.resize(horizontal, vertical, anchor) - - def rotate(self, angle, anchor=None): - self.app.rotate(angle, anchor) - - def translate(self, delta_x, delta_y): - """Moves the position relative to its current position.""" - self.app.translate(delta_x, delta_y) - - def unlink(self): - """Unlinks the layer set.""" - self.app.unlink() - - def __iter__(self): + def __iter__(self) -> Iterator[Layer]: for layer in self.app: - yield layer + yield Layer(layer) diff --git a/photoshop/api/_layerSets.py b/photoshop/api/_layerSets.py index 8b5b3aef..1f92544b 100644 --- a/photoshop/api/_layerSets.py +++ b/photoshop/api/_layerSets.py @@ -1,62 +1,19 @@ -# Import third-party modules -from comtypes import ArgumentError - # Import local modules from photoshop.api._core import Photoshop from photoshop.api._layerSet import LayerSet -from photoshop.api.errors import PhotoshopPythonAPIError - - -class LayerSets(Photoshop): +from photoshop.api.collections import ( + CollectionOfNamedObjects, + CollectionOfRemovables, + CollectionWithAdd, +) + + +class LayerSets( + CollectionWithAdd[LayerSet, int | str], + CollectionOfRemovables[LayerSet, int | str], + CollectionOfNamedObjects[LayerSet, int | str], +): """The layer sets collection in the document.""" - def __init__(self, parent): - super().__init__(parent=parent) - self._flag_as_method( - "add", - "item", - "removeAll", - ) - - def __len__(self): - return self.length - - def __iter__(self): - for layer_set in self.app: - yield layer_set - - def __getitem__(self, key: str): - """Access a given LayerSet using dictionary key lookup.""" - try: - return LayerSet(self.app[key]) - except ArgumentError: - raise PhotoshopPythonAPIError(f'Could not find a LayerSet named "{key}"') - - @property - def _layerSets(self): - return list(self.app) - - @property - def length(self) -> int: - """Number of elements in the collection.""" - return len(self._layerSets) - - def add(self): - return LayerSet(self.app.add()) - - def item(self, index: int) -> LayerSet: - return LayerSet(self.app.item(index)) - - def removeAll(self): - self.app.removeAll() - - def getByIndex(self, index: int): - """Access LayerSet using list index lookup.""" - return LayerSet(self._layerSets[index]) - - def getByName(self, name: str) -> LayerSet: - """Get the first element in the collection with the provided name.""" - for layer in self.app: - if name == layer.name: - return LayerSet(layer) - raise PhotoshopPythonAPIError(f'Could not find a LayerSet named "{name}"') + def __init__(self, parent: Photoshop | None = None) -> None: + super().__init__(LayerSet, parent) diff --git a/photoshop/api/_layers.py b/photoshop/api/_layers.py index dfe1d126..798dd4f3 100644 --- a/photoshop/api/_layers.py +++ b/photoshop/api/_layers.py @@ -1,50 +1,54 @@ # Import local modules +from typing import Any, Iterator + from photoshop.api._artlayer import ArtLayer from photoshop.api._core import Photoshop -from photoshop.api.errors import PhotoshopPythonAPIError +from photoshop.api._layer import Layer +from photoshop.api._layerSet import LayerSet # pylint: disable=too-many-public-methods class Layers(Photoshop): """The layers collection in the document.""" - def __init__(self, parent): + def __init__(self, parent: Photoshop | None = None) -> None: super().__init__(parent=parent) - self._flag_as_method( - "add", - "item", - ) + self._flag_as_method("add", "item", "removeAll") - @property - def _layers(self): - return list(self.app) + def _get_appropriate_layer(self, layer: Any) -> ArtLayer | LayerSet: + try: + layer.layers + return LayerSet(layer) + except NameError: + return ArtLayer(layer) - def __len__(self): + def __len__(self) -> int: return self.length - def __getitem__(self, key): - item = self._layers[key] - return ArtLayer(item) + def __getitem__(self, key: int) -> ArtLayer | LayerSet: + for idx, layer in enumerate(self.app): + if idx == key: + return self._get_appropriate_layer(layer) + raise KeyError(f"Key '{key}' was not found in {type(self)}.") @property - def length(self): - return len(self._layers) + def length(self) -> int: + return len(list(self.app)) - def removeAll(self): + def removeAll(self) -> None: """Deletes all elements.""" - for layer in self.app: - ArtLayer(layer).remove() + self.app.removeAll() - def item(self, index): - return ArtLayer(self.app.item(index)) + def item(self, index: int) -> Layer: + return self[index] - def __iter__(self): - for layer in self._layers: - yield ArtLayer(layer) + def __iter__(self) -> Iterator[ArtLayer | LayerSet]: + for layer in self.app: + yield self._get_appropriate_layer(layer) - def getByName(self, name: str) -> ArtLayer: + def getByName(self, name: str) -> ArtLayer | LayerSet | None: """Get the first element in the collection with the provided name.""" for layer in self.app: if layer.name == name: - return ArtLayer(layer) - raise PhotoshopPythonAPIError("X") + return self._get_appropriate_layer(layer) + return None diff --git a/photoshop/api/_measurement_log.py b/photoshop/api/_measurement_log.py index 1e2d9720..519ac27c 100644 --- a/photoshop/api/_measurement_log.py +++ b/photoshop/api/_measurement_log.py @@ -1,21 +1,29 @@ # Import local modules +from typing import Sequence + from photoshop.api._core import Photoshop +from photoshop.api.enumerations import MeasurementRange class MeasurementLog(Photoshop): """The log of measurements taken.""" - def __init__(self, parent): + def __init__(self, parent: Photoshop | None = None) -> None: super().__init__(parent=parent) self._flag_as_method( "exportMeasurements", "deleteMeasurements", ) - def exportMeasurements(self, file_path: str, range_: int = None, data_point=None): + def exportMeasurements( + self, + file_path: str, + range_: MeasurementRange | None = None, + data_point: Sequence[str] | None = None, + ): if data_point is None: data_point = [] self.app.exportMeasurements(file_path, range_, data_point) - def deleteMeasurements(self, range_: int): + def deleteMeasurements(self, range_: MeasurementRange) -> None: self.app.deleteMeasurements(range_) diff --git a/photoshop/api/_notifier.py b/photoshop/api/_notifier.py index e0f651de..349d96c9 100644 --- a/photoshop/api/_notifier.py +++ b/photoshop/api/_notifier.py @@ -5,6 +5,7 @@ Notifiers must be enabled using the Application.notifiersEnabled property """ + # Import built-in modules from pathlib import Path @@ -13,19 +14,19 @@ class Notifier(Photoshop): - def __init__(self, parent=None): - super().__init__() + def __init__(self, parent: Photoshop | None = None) -> None: + super().__init__(parent=parent) self._flag_as_method( "remove", ) @property - def event(self): + def event(self) -> str: """The event identifier, a four-character code or a unique string.""" return self.app.event @property - def eventClass(self): + def eventClass(self) -> str: """The class identifier, a four-character code or a unique string. When an event applies to multiple types of objects, use this @@ -42,7 +43,7 @@ def eventFile(self) -> Path: activates the notifier.""" return Path(self.app.eventFile) - def remove(self): + def remove(self) -> None: """Deletes this object. You can also remove a Notifier object @@ -53,4 +54,4 @@ def remove(self): Photoshop CC help for more information. """ - return self.app.remove() + self.app.remove() diff --git a/photoshop/api/_notifiers.py b/photoshop/api/_notifiers.py index 3ecbcdc3..735f8bba 100644 --- a/photoshop/api/_notifiers.py +++ b/photoshop/api/_notifiers.py @@ -11,46 +11,32 @@ """ # Import built-in modules -from typing import Any -from typing import Optional +from typing import TYPE_CHECKING # Import local modules from photoshop.api._core import Photoshop from photoshop.api._notifier import Notifier +from photoshop.api.collections import CollectionOfRemovables -class Notifiers(Photoshop): +class Notifiers(CollectionOfRemovables[Notifier, int]): """The `notifiers` currently configured (in the Scripts Events Manager menu in the application).""" - def __init__(self, parent: Optional[Any] = None): - super().__init__(parent=parent) - self._flag_as_method( - "add", - "removeAll", - ) + if TYPE_CHECKING: + from photoshop.api.application import Application - @property - def _notifiers(self) -> list: - return [n for n in self.app] + parent: Application - def __len__(self): - return self.length + def __init__(self, parent: Photoshop | None = None) -> None: + super().__init__(type=Notifier, parent=parent) + self._flag_as_method("add") - def __iter__(self): - for app in self.app: - yield app - - def __getitem__(self, item): - return self._notifiers[item] - - @property - def length(self): - return len(self._notifiers) - - def add(self, event, event_file: Optional[Any] = None, event_class: Optional[Any] = None) -> Notifier: + def add( + self, event: str, event_file: str, event_class: str | None = None + ) -> Notifier: self.parent.notifiersEnabled = True return Notifier(self.app.add(event, event_file, event_class)) - def removeAll(self): + def removeAll(self) -> None: self.app.removeAll() self.parent.notifiersEnabled = False diff --git a/photoshop/api/_preferences.py b/photoshop/api/_preferences.py index c4d57356..e70416a6 100644 --- a/photoshop/api/_preferences.py +++ b/photoshop/api/_preferences.py @@ -1,36 +1,67 @@ # Import built-in modules +from os import PathLike from pathlib import Path # Import local modules from photoshop.api._core import Photoshop +from photoshop.api.enumerations import ( + ColorPicker, + EditLogItemsType, + FontPreviewType, + FontSize, + GridLineStyle, + GridSize, + GuideLineStyle, + OtherPaintingCursors, + PaintingCursors, + PointType, + QueryStateType, + ResampleMethod, + SaveBehavior, + TypeUnits, + Units, +) class Preferences(Photoshop): """The application preference settings.""" - def __init__(self, parent): + def __init__(self, parent: Photoshop | None = None) -> None: super().__init__(parent=parent) @property - def additionalPluginFolder(self): + def additionalPluginFolder(self) -> Path: """The path to an additional plug-in folder.""" return Path(self.app.additionalPluginFolder) + @additionalPluginFolder.setter + def additionalPluginFolder(self, value: str | PathLike[str]) -> None: + """The path to an additional plug-in folder.""" + self.app.additionalPluginFolder = str(value) + @property - def appendExtension(self): - return self.app.appendExtension + def appendExtension(self) -> SaveBehavior: + return SaveBehavior(self.app.appendExtension) + + @appendExtension.setter + def appendExtension(self, value: SaveBehavior) -> None: + self.app.appendExtension = value @property def askBeforeSavingLayeredTIFF(self) -> bool: return self.app.askBeforeSavingLayeredTIFF + @askBeforeSavingLayeredTIFF.setter + def askBeforeSavingLayeredTIFF(self, value: bool) -> None: + self.app.askBeforeSavingLayeredTIFF = value + @property def autoUpdateOpenDocuments(self) -> bool: """True to automatically update open documents.""" return self.app.autoUpdateOpenDocuments @autoUpdateOpenDocuments.setter - def autoUpdateOpenDocuments(self, boolean: bool): + def autoUpdateOpenDocuments(self, boolean: bool) -> None: """True to automatically update open documents.""" self.app.autoUpdateOpenDocuments = boolean @@ -40,109 +71,378 @@ def beepWhenDone(self) -> bool: return self.app.beepWhenDone @beepWhenDone.setter - def beepWhenDone(self, boolean): + def beepWhenDone(self, boolean: bool) -> None: self.app.beepWhenDone = boolean @property - def colorChannelsInColor(self): + def colorChannelsInColor(self) -> bool: """True to display component channels in the Channels palette in color.""" return self.app.colorChannelsInColor @colorChannelsInColor.setter - def colorChannelsInColor(self, value): + def colorChannelsInColor(self, value: bool) -> None: self.app.colorChannelsInColor = value @property - def colorPicker(self): + def colorPicker(self) -> ColorPicker: """The preferred color selection tool.""" - return self.app.colorPicker + return ColorPicker(self.app.colorPicker) @colorPicker.setter - def colorPicker(self, value): + def colorPicker(self, value: ColorPicker) -> None: self.app.colorPicker = value @property - def columnGutter(self): + def columnGutter(self) -> float: return self.app.columnGutter @columnGutter.setter - def columnGutter(self, value): + def columnGutter(self, value: float) -> None: self.app.columnGutter = value @property - def columnWidth(self): + def columnWidth(self) -> float: return self.app.columnWidth @columnWidth.setter - def columnWidth(self, value): + def columnWidth(self, value: float) -> None: self.app.columnWidth = value @property - def createFirstSnapshot(self): + def createFirstSnapshot(self) -> bool: """Automatically make the first snapshot when a new document is created.""" return self.app.createFirstSnapshot @createFirstSnapshot.setter - def createFirstSnapshot(self, boolean): + def createFirstSnapshot(self, boolean: bool) -> None: self.app.createFirstSnapshot = boolean @property - def dynamicColorSliders(self): + def dynamicColorSliders(self) -> bool: return self.app.dynamicColorSliders @dynamicColorSliders.setter - def dynamicColorSliders(self, boolean): + def dynamicColorSliders(self, boolean: bool) -> None: self.app.dynamicColorSliders = boolean @property - def editLogItems(self) -> bool: + def editLogItems(self) -> EditLogItemsType: """The preferred level of detail in the history log.""" - return self.app.editLogItems + return EditLogItemsType(self.app.editLogItems) @editLogItems.setter - def editLogItems(self, boolean: bool): + def editLogItems(self, value: EditLogItemsType) -> None: """The preferred level of detail in the history log. Valid only when useHistoryLog = True """ - self.app.editLogItems = boolean + self.app.editLogItems = value @property - def exportClipboard(self): + def exportClipboard(self) -> bool: """Retain Photoshop contents on the clipboard after exit the app.""" return self.app.exportClipboard @exportClipboard.setter - def exportClipboard(self, boolean: bool): + def exportClipboard(self, boolean: bool) -> None: self.app.exportClipboard = boolean @property - def fontPreviewSize(self): - return self.app.fontPreviewSize + def fontPreviewSize(self) -> FontPreviewType: + return FontPreviewType(self.app.fontPreviewSize) @fontPreviewSize.setter - def fontPreviewSize(self, value): + def fontPreviewSize(self, value: FontPreviewType) -> None: self.app.fontPreviewSize = value @property - def fullSizePreview(self): + def fullSizePreview(self) -> bool: return self.app.fullSizePreview @fullSizePreview.setter - def fullSizePreview(self, value): + def fullSizePreview(self, value: bool) -> None: self.app.fullSizePreview = value @property - def gamutWarningOpacity(self): + def gamutWarningOpacity(self) -> float: return self.app.gamutWarningOpacity + @gamutWarningOpacity.setter + def gamutWarningOpacity(self, value: float) -> None: + self.app.gamutWarningOpacity = value + + @property + def gridSize(self) -> GridSize: + return GridSize(self.app.gridSize) + + @gridSize.setter + def gridSize(self, value: GridSize) -> None: + self.app.gridSize = value + + @property + def gridStyle(self) -> GridLineStyle: + return GridLineStyle(self.app.gridStyle) + + @gridStyle.setter + def gridStyle(self, value: GridLineStyle) -> None: + self.app.gridStyle = value + + @property + def gridSubDivisions(self) -> int: + return self.app.gridSubDivisions + + @gridSubDivisions.setter + def gridSubDivisions(self, value: int) -> None: + self.app.gridSubDivisions = value + + @property + def guideStyle(self) -> GuideLineStyle: + return GuideLineStyle(self.app.guideStyle) + + @guideStyle.setter + def guideStyle(self, value: GuideLineStyle) -> None: + self.app.guideStyle = value + + @property + def iconPreview(self) -> bool: + return self.app.iconPreview + + @iconPreview.setter + def iconPreview(self, value: bool) -> None: + self.app.iconPreview = value + + @property + def imageCacheLevels(self) -> int: + return self.app.imageCacheLevels + + @imageCacheLevels.setter + def imageCacheLevels(self, value: int) -> None: + self.app.imageCacheLevels = value + + @property + def imagePreviews(self) -> SaveBehavior: + return SaveBehavior(self.app.imagePreviews) + + @imagePreviews.setter + def imagePreviews(self, value: SaveBehavior) -> None: + self.app.imagePreviews = value + + @property + def interpolation(self) -> ResampleMethod: + return ResampleMethod(self.app.interpolation) + + @interpolation.setter + def interpolation(self, value: ResampleMethod) -> None: + self.app.interpolation = value + + @property + def keyboardZoomResizesWindows(self) -> bool: + return self.app.keyboardZoomResizesWindows + + @keyboardZoomResizesWindows.setter + def keyboardZoomResizesWindows(self, value: bool) -> None: + self.app.keyboardZoomResizesWindows = value + + @property + def maximizeCompatibility(self) -> QueryStateType: + return QueryStateType(self.app.maximizeCompatibility) + + @maximizeCompatibility.setter + def maximizeCompatibility(self, value: QueryStateType) -> None: + self.app.maximizeCompatibility = value + + @property + def maxRAMuse(self) -> int: + return self.app.maxRAMuse + + @maxRAMuse.setter + def maxRAMuse(self, value: int) -> None: + self.app.maxRAMuse = value + + @property + def nonLinearHistory(self) -> bool: + return self.app.nonLinearHistory + + @nonLinearHistory.setter + def nonLinearHistory(self, value: bool) -> None: + self.app.nonLinearHistory = value + + @property + def numberofHistoryStates(self) -> int: + return self.app.numberofHistoryStates + + @numberofHistoryStates.setter + def numberofHistoryStates(self, value: int) -> None: + self.app.numberofHistoryStates = value + + @property + def otherCursors(self) -> OtherPaintingCursors: + return OtherPaintingCursors(self.app.otherCursors) + + @otherCursors.setter + def otherCursors(self, value: OtherPaintingCursors) -> None: + self.app.otherCursors = value + + @property + def paintingCursors(self) -> PaintingCursors: + return PaintingCursors(self.app.paintingCursors) + + @paintingCursors.setter + def paintingCursors(self, value: PaintingCursors) -> None: + self.app.paintingCursors = value + + @property + def pixelDoubling(self) -> bool: + return self.app.pixelDoubling + + @pixelDoubling.setter + def pixelDoubling(self, value: bool) -> None: + self.app.pixelDoubling = value + + @property + def pointSize(self) -> PointType: + return PointType(self.app.pointSize) + + @pointSize.setter + def pointSize(self, value: PointType) -> None: + self.app.pointSize = value + + @property + def recentFileListLength(self) -> int: + return self.app.recentFileListLength + + @recentFileListLength.setter + def recentFileListLength(self, value: int) -> None: + self.app.recentFileListLength = value + @property - def rulerUnits(self): - return self.app.rulerUnits + def rulerUnits(self) -> Units: + return Units(self.app.rulerUnits) @rulerUnits.setter - def rulerUnits(self, value): + def rulerUnits(self, value: Units) -> None: self.app.rulerUnits = value + + @property + def saveLogItems(self) -> Path: + return Path(self.app.saveLogItems) + + @saveLogItems.setter + def saveLogItems(self, value: str | PathLike[str]) -> None: + self.app.saveLogItems = str(value) + + @property + def savePaletteLocations(self) -> bool: + return self.app.savePaletteLocations + + @savePaletteLocations.setter + def savePaletteLocations(self, value: bool) -> None: + self.app.savePaletteLocations = value + + @property + def showAsianTextOptions(self) -> bool: + return self.app.showAsianTextOptions + + @showAsianTextOptions.setter + def showAsianTextOptions(self, value: bool) -> None: + self.app.showAsianTextOptions = value + + @property + def showEnglishFontNames(self) -> bool: + return self.app.showEnglishFontNames + + @showEnglishFontNames.setter + def showEnglishFontNames(self, value: bool) -> None: + self.app.showEnglishFontNames = value + + @property + def showSliceNumber(self) -> bool: + return self.app.showSliceNumber + + @showSliceNumber.setter + def showSliceNumber(self, value: bool) -> None: + self.app.showSliceNumber = value + + @property + def showToolTips(self) -> bool: + return self.app.showToolTips + + @showToolTips.setter + def showToolTips(self, value: bool) -> None: + self.app.showToolTips = value + + @property + def smartQuotes(self) -> bool: + return self.app.smartQuotes + + @smartQuotes.setter + def smartQuotes(self, value: bool) -> None: + self.app.smartQuotes = value + + # textFontSize doesn't seem to be accessible via the COM API + @property + def textFontSize(self) -> FontSize: + return FontSize(self.eval_javascript("app.preferences.textFontSize")) + + @textFontSize.setter + def textFontSize(self, value: FontSize) -> None: + self.eval_javascript(f"app.preferences.textFontSize = {FontSize(value)}") + + @property + def typeUnits(self) -> TypeUnits: + return TypeUnits(self.app.typeUnits) + + @typeUnits.setter + def typeUnits(self, value: TypeUnits) -> None: + self.app.typeUnits = value + + @property + def useAdditionalPluginFolder(self) -> bool: + return self.app.useAdditionalPluginFolder + + @useAdditionalPluginFolder.setter + def useAdditionalPluginFolder(self, value: bool) -> None: + self.app.useAdditionalPluginFolder = value + + @property + def useHistoryLog(self) -> bool: + return self.app.useHistoryLog + + @useHistoryLog.setter + def useHistoryLog(self, value: bool) -> None: + self.app.useHistoryLog = value + + @property + def useLowerCaseExtension(self) -> bool: + return self.app.useLowerCaseExtension + + @useLowerCaseExtension.setter + def useLowerCaseExtension(self, value: bool) -> None: + self.app.useLowerCaseExtension = value + + @property + def useShiftKeyForToolSwitch(self) -> bool: + return self.app.useShiftKeyForToolSwitch + + @useShiftKeyForToolSwitch.setter + def useShiftKeyForToolSwitch(self, value: bool) -> None: + self.app.useShiftKeyForToolSwitch = value + + @property + def useVideoAlpha(self) -> bool: + return self.app.useVideoAlpha + + @useVideoAlpha.setter + def useVideoAlpha(self, value: bool) -> None: + self.app.useVideoAlpha = value + + @property + def windowsThumbnail(self) -> bool: + return self.app.windowsThumbnail + + @windowsThumbnail.setter + def windowsThumbnail(self, value: bool) -> None: + self.app.windowsThumbnail = value diff --git a/photoshop/api/_selection.py b/photoshop/api/_selection.py index 63e19e14..ceb8798b 100644 --- a/photoshop/api/_selection.py +++ b/photoshop/api/_selection.py @@ -1,9 +1,15 @@ """The selected area of the document or layer.""" # Import local modules +from photoshop.api._channel import Channel from photoshop.api._core import Photoshop -from photoshop.api.enumerations import ColorBlendMode -from photoshop.api.enumerations import SelectionType +from photoshop.api._document import Document +from photoshop.api.enumerations import ( + AnchorPosition, + ColorBlendMode, + SelectionType, + StrokeLocation, +) from photoshop.api.solid_color import SolidColor @@ -11,7 +17,7 @@ class Selection(Photoshop): """The selected area of the document.""" - def __init__(self, parent=None): + def __init__(self, parent: Photoshop | None = None) -> None: super().__init__(parent=parent) self._flag_as_method( "clear", @@ -32,6 +38,7 @@ def __init__(self, parent=None): "rotateBoundary", "select", "selectBorder", + "selectAll", "similar", "smooth", "store", @@ -41,44 +48,46 @@ def __init__(self, parent=None): ) @property - def bounds(self): + def bounds(self) -> tuple[float, float, float, float]: return self.app.bounds - def parent(self): - return self.app.parent + def parent(self) -> Document: + return Document(self.app.parent) @property - def solid(self): + def solid(self) -> bool: return self.app.solid - @property - def typename(self): - return self.app.typename - - def clear(self): + def clear(self) -> None: """Clears the selection and does not copy it to the clipboard.""" self.app.clear() - def contract(self, contract_by): + def contract(self, contract_by: float) -> None: """Contracts the selection.""" self.app.contract(contract_by) - def copy(self): + def copy(self, merge: bool = False) -> None: """Copies the selection to the clipboard.""" - self.app.copy() + self.app.copy(merge) - def cut(self): - """Cuts the current selection to the clipboard.""" + def cut(self) -> None: + """Clears the current selection and copies it to the clipboard.""" self.app.cut() - def select(self, *args, **kwargs): - return self.app.select(*args, **kwargs) - - def deselect(self): + def select( + self, + region: tuple[float, float, float, float], + selection_type: SelectionType | None = None, + feather: float = 0, + anti_alias: bool = True, + ) -> None: + self.app.select(region, selection_type, feather, anti_alias) + + def deselect(self) -> None: """Deselects the current selection.""" - return self.app.deselect() + self.app.deselect() - def expand(self, by: int): + def expand(self, by: float) -> None: """Expands the selection. Args: @@ -87,26 +96,26 @@ def expand(self, by: int): """ self.app.expand(by) - def feather(self, by: int): + def feather(self, by: float) -> None: """Feathers the edges of the selection. Args: by: The amount to feather the edge. """ - return self.app.feather(by) + self.app.feather(by) def fill( self, fill_type: SolidColor, - mode: ColorBlendMode = None, - opacity=None, - preserve_transparency=None, - ): + mode: ColorBlendMode | None = None, + opacity: float | None = None, + preserve_transparency: bool = False, + ) -> None: """Fills the selection.""" - return self.app.fill(fill_type, mode, opacity, preserve_transparency) + self.app.fill(fill_type, mode, opacity, preserve_transparency) - def grow(self, tolerance, anti_alias): + def grow(self, tolerance: int, anti_alias: bool) -> None: """Grows the selection to include all adjacent pixels falling within The specified tolerance range. @@ -117,38 +126,58 @@ def grow(self, tolerance, anti_alias): """ - return self.app.grow(tolerance, anti_alias) + self.app.grow(tolerance, anti_alias) - def invert(self): + def invert(self) -> None: """Inverts the selection.""" self.app.invert() - def load(self, from_channel, combination, inverting): + def load( + self, + from_channel: Channel, + combination: SelectionType | None = None, + inverting: bool = False, + ) -> None: """Loads the selection from the specified channel.""" - return self.app.load(from_channel, combination, inverting) + self.app.load(from_channel, combination, inverting) - def makeWorkPath(self, tolerance): + def makeWorkPath(self, tolerance: float) -> None: """Makes this selection item the workpath for this document.""" self.app.makeWorkPath(tolerance) - def resize(self, horizontal, vertical, anchor): + def resize( + self, horizontal: float, vertical: float, anchor: AnchorPosition + ) -> None: """Resizes the selected area to the specified dimensions and anchor position.""" self.app.resize(horizontal, vertical, anchor) - def resizeBoundary(self, horizontal, vertical, anchor): + def resizeBoundary( + self, horizontal: float, vertical: float, anchor: AnchorPosition + ) -> None: """Scales the boundary of the selection.""" self.app.resizeBoundary(horizontal, vertical, anchor) - def rotate(self, angle, anchor): + def rotate(self, angle: float, anchor: AnchorPosition) -> None: """Rotates the object.""" self.app.rotate(angle, anchor) - def rotateBoundary(self, angle, anchor): + def rotateBoundary(self, angle: float, anchor: AnchorPosition) -> None: """Rotates the boundary of the selection.""" self.app.rotateBoundary(angle, anchor) - def stroke(self, strokeColor, width, location, mode, opacity, preserveTransparency): + def selectAll(self) -> None: + self.app.selectAll() + + def stroke( + self, + strokeColor: SolidColor, + width: int, + location: StrokeLocation | None = None, + mode: ColorBlendMode | None = None, + opacity: int = 100, + preserveTransparency: bool = False, + ) -> None: """Strokes the selection. Args: @@ -161,9 +190,11 @@ def stroke(self, strokeColor, width, location, mode, opacity, preserveTransparen preserveTransparency (bool): If true, preserves transparency. """ - return self.app.stroke(strokeColor, width, location, mode, opacity, preserveTransparency) + self.app.stroke( + strokeColor, width, location, mode, opacity, preserveTransparency + ) - def selectBorder(self, width): + def selectBorder(self, width: float) -> None: """Selects the selection border only (in the specified width); subsequent actions do not affect the selected area within the borders. @@ -171,24 +202,26 @@ def selectBorder(self, width): width (int): The width of the border selection. """ - return self.app.selectBorder(width) + self.app.selectBorder(width) - def similar(self, tolerance, antiAlias): - return self.app.similar(tolerance, antiAlias) + def similar(self, tolerance: int, anti_alias: bool = True) -> None: + self.app.similar(tolerance, anti_alias) - def smooth(self, radius): + def smooth(self, radius: int) -> None: """Cleans up stray pixels left inside or outside a color-based selection (within the radius specified in pixels).""" - return self.app.smooth(radius) + self.app.smooth(radius) - def store(self, into, combination=SelectionType.ReplaceSelection): + def store( + self, into: Channel, combination: SelectionType = SelectionType.ReplaceSelection + ) -> None: """Saves the selection as a channel.""" - return self.app.store(into, combination) + self.app.store(into, combination) - def translate(self, deltaX, deltaY): + def translate(self, deltaX: float = 0, deltaY: float = 0) -> None: """Moves the object relative to its current position.""" - return self.app.translate(deltaX, deltaY) + self.app.translate(deltaX, deltaY) - def translateBoundary(self, deltaX, deltaY): + def translateBoundary(self, deltaX: float = 0, deltaY: float = 0) -> None: """Moves the boundary of selection relative to its current position.""" - return self.app.translateBoundary(deltaX, deltaY) + self.app.translateBoundary(deltaX, deltaY) diff --git a/photoshop/api/_text_fonts.py b/photoshop/api/_text_fonts.py index dbe43d63..6cefc525 100644 --- a/photoshop/api/_text_fonts.py +++ b/photoshop/api/_text_fonts.py @@ -1,35 +1,35 @@ # Import built-in modules -from typing import Any -from typing import Union +from typing import Iterator, TypeVar, overload # Import third-party modules -from comtypes import ArgumentError -from comtypes import COMError +from comtypes import ArgumentError, COMError # Import local modules from photoshop.api._core import Photoshop from photoshop.api.errors import PhotoshopPythonAPIError from photoshop.api.text_font import TextFont +T = TypeVar("T") + class TextFonts(Photoshop): """An installed font.""" - def __init__(self, parent=None): + def __init__(self, parent: Photoshop | None = None) -> None: super().__init__(parent=parent) """ MAGIC METHODS """ - def __len__(self): + def __len__(self) -> int: return self.length - def __iter__(self): + def __iter__(self) -> Iterator[TextFont]: for font in self.app: yield TextFont(font) - def __contains__(self, name: str): + def __contains__(self, name: str) -> bool: """Check if a font is installed. Lookup by font postScriptName (fastest) or name. Args: @@ -50,7 +50,7 @@ def __contains__(self, name: str): continue return False - def __getitem__(self, key: str): + def __getitem__(self, key: str) -> TextFont: """Access a given TextFont using dictionary key lookup, must provide the postScriptName. Args: @@ -63,13 +63,21 @@ def __getitem__(self, key: str): try: return TextFont(self.app[key]) except ArgumentError: - raise PhotoshopPythonAPIError(f'Could not find a font with postScriptName "{key}"') + raise PhotoshopPythonAPIError( + f'Could not find a font with postScriptName "{key}"' + ) """ METHODS """ - def get(self, key: str, default: Any = None) -> Union[TextFont, Any]: + @overload + def get(self, key: str, default: T) -> TextFont | T: ... + + @overload + def get(self, key: str) -> TextFont | None: ... + + def get(self, key: str, default: T | None = None) -> TextFont | T | None: """ Accesses a given TextFont using dictionary key lookup of postScriptName, returns default if not found. @@ -86,7 +94,7 @@ def get(self, key: str, default: Any = None) -> Union[TextFont, Any]: except (KeyError, ArgumentError): return default - def getByName(self, name: str) -> TextFont: + def getByName(self, name: str) -> TextFont | None: """Gets the font by the font name. Args: @@ -100,17 +108,12 @@ def getByName(self, name: str) -> TextFont: for font in self.app: if font.name == name: return TextFont(font) - raise PhotoshopPythonAPIError('Could not find a TextFont named "{name}"') """ PROPERTIES """ @property - def _fonts(self): - return [a for a in self.app] - - @property - def length(self): + def length(self) -> int: """The number pf elements in the collection.""" - return len(self._fonts) + return len(list(self.app)) diff --git a/photoshop/api/action_descriptor.py b/photoshop/api/action_descriptor.py index 6ecd4b0b..4b05205d 100644 --- a/photoshop/api/action_descriptor.py +++ b/photoshop/api/action_descriptor.py @@ -7,17 +7,16 @@ """ -# Import built-in modules -from pathlib import Path - # Import local modules +from os import PathLike + from photoshop.api._core import Photoshop from photoshop.api.action_list import ActionList from photoshop.api.action_reference import ActionReference -from photoshop.api.enumerations import DescValueType +from photoshop.api.base_action import BaseAction -class ActionDescriptor(Photoshop): +class ActionDescriptor(BaseAction): """A record of key-value pairs for actions, such as those included on the Adobe Photoshop Actions menu. The ActionDescriptor class is part of the Action Manager functionality. @@ -27,30 +26,11 @@ class ActionDescriptor(Photoshop): object_name = "ActionDescriptor" - def __init__(self): - super().__init__() + def __init__(self, parent: Photoshop | None = None) -> None: + super().__init__(parent=parent) self._flag_as_method( - "clear", "erase", "fromStream", - "getBoolean", - "getClass", - "getData", - "getDouble", - "getEnumerationType", - "getEnumerationValue", - "getInteger", - "getKey", - "getLargeInteger", - "getList", - "getObjectType", - "getObjectValue", - "getPath", - "getReference", - "getString", - "getType", - "getUnitDoubleType", - "getUnitDoubleValue", "hasKey", "isEqual", "putBoolean", @@ -69,120 +49,20 @@ def __init__(self): "toSteadm", ) - @property - def count(self): - """The number of keys contained in the descriptor.""" - return self.app.count - - def clear(self): - """Clears the descriptor.""" - self.app.clear() - - def erase(self, key: int): + def erase(self, key: int) -> None: """Erases a key form the descriptor.""" self.app.erase(key) - def fromStream(self, value: str): - """Create a descriptor from a stream of bytes. - - for reading from disk. - - """ + def fromStream(self, value: str) -> None: + """Create a descriptor from a stream of bytes, + for reading from disk.""" self.app.fromStream(value) - def getBoolean(self, key: int) -> int: - """Gets the text_font of a key of type boolean. - - Args: - key (str): key of type boolean. - - Returns: - bool: The text_font of a key of type boolean. - - """ - return self.app.getBoolean(key) - - def getClass(self, key): - """Gets the text_font of a key of type class. - - Args: - key (str): The key of type class. - - Returns: - int: The text_font of a key of type class. - - """ - return self.app.getClass(key) - - def getData(self, key: int) -> int: - """Gets raw byte data as a string value.""" - return self.app.getData(key) - - def getDouble(self, key: int) -> float: - """Gets the value of a key of type double.""" - return self.app.getDouble(key) - - def getEnumerationType(self, index: int) -> int: - """Gets the enumeration type of a key.""" - return self.app.getEnumerationType(index) - - def getEnumerationValue(self, index: int) -> int: - """Gets the enumeration value of a key.""" - return self.app.getEnumerationValue(index) - - def getInteger(self, index: int) -> int: - """Gets the value of a key of type integer.""" - return self.app.getInteger(index) - - def getKey(self, index: int) -> int: - """Gets the ID of the key provided by index.""" - return self.app.getKey(index) - - def getLargeInteger(self, index: int) -> int: - """Gets the value of a key of type large integer.""" - return self.app.getLargeInteger(index) - - def getList(self, index: int) -> ActionList: - """Gets the value of a key of type list.""" - return ActionList(self.app.getList(index)) - - def getObjectType(self, key: int) -> int: - """Gets the class ID of an object in a key of type object.""" - return self.app.getObjectType(key) - - def getObjectValue(self, key: int) -> int: - """Get the class ID of an object in a key of type object.""" - return self.app.getObjectValue(key) - - def getPath(self, key: int) -> Path: - """Gets the value of a key of type.""" - return Path(self.app.getPath(key)) - - def getReference(self, key: int) -> ActionReference: - """Gets the value of a key of type.""" - return ActionReference(self.app.getReference(key)) - - def getString(self, key: int) -> str: - """Gets the value of a key of type.""" - return self.app.getString(key) - - def getType(self, key: int) -> DescValueType: - """Gets the type of a key.""" - return DescValueType(self.app.getType(key)) - - def getUnitDoubleType(self, key: int) -> int: - """Gets the unit type of a key of type UnitDouble.""" - return self.app.getUnitDoubleType(key) - - def getUnitDoubleValue(self, key: int) -> float: - """Gets the unit type of a key of type UnitDouble.""" - return self.app.getUnitDoubleValue(key) - def hasKey(self, key: int) -> bool: """Checks whether the descriptor contains the provided key.""" return self.app.hasKey(key) - def isEqual(self, otherDesc) -> bool: + def isEqual(self, otherDesc: "ActionDescriptor") -> bool: """Determines whether the descriptor is the same as another descriptor. Args: @@ -191,55 +71,55 @@ def isEqual(self, otherDesc) -> bool: """ return self.app.isEqual(otherDesc) - def putBoolean(self, key: int, value: bool): + def putBoolean(self, key: int, value: bool) -> None: """Sets the value for a key whose type is boolean.""" self.app.putBoolean(key, value) - def putClass(self, key: int, value: int): + def putClass(self, key: int, value: int) -> None: """Sets the value for a key whose type is class.""" self.app.putClass(key, value) - def putData(self, key: int, value: str): + def putData(self, key: int, value: str) -> None: """Puts raw byte data as a string value.""" self.app.putData(key, value) - def putDouble(self, key: int, value: float): + def putDouble(self, key: int, value: float) -> None: """Sets the value for a key whose type is double.""" self.app.putDouble(key, value) - def putEnumerated(self, key: int, enum_type: int, value: int): + def putEnumerated(self, key: int, enum_type: int, value: int) -> None: """Sets the enumeration type and value for a key.""" self.app.putEnumerated(key, enum_type, value) - def putInteger(self, key: int, value: int): + def putInteger(self, key: int, value: int) -> None: """Sets the value for a key whose type is integer.""" self.app.putInteger(key, value) - def putLargeInteger(self, key: int, value: int): + def putLargeInteger(self, key: int, value: int) -> None: """Sets the value for a key whose type is large integer.""" self.app.putLargeInteger(key, value) - def putList(self, key: int, value: ActionList): + def putList(self, key: int, value: "ActionList") -> None: """Sets the value for a key whose type is an ActionList object.""" self.app.putList(key, value) - def putObject(self, key: int, class_id: int, value): + def putObject(self, key: int, class_id: int, value: "ActionDescriptor") -> None: """Sets the value for a key whose type is an object.""" self.app.putObject(key, class_id, value) - def putPath(self, key: int, value: str): + def putPath(self, key: int, value: str | PathLike[str]) -> None: """Sets the value for a key whose type is path.""" - self.app.putPath(key, value) + self.app.putPath(key, str(value)) - def putReference(self, key: int, value: ActionReference): + def putReference(self, key: int, value: ActionReference) -> None: """Sets the value for a key whose type is an object reference.""" self.app.putReference(key, value) - def putString(self, key: int, value: str): + def putString(self, key: int, value: str) -> None: """Sets the value for a key whose type is string.""" self.app.putString(key, value) - def putUnitDouble(self, key: int, unit_id: int, value: float): + def putUnitDouble(self, key: int, unit_id: int, value: float) -> None: """Sets the value for a key whose type is a unit value formatted as double.""" self.app.putUnitDouble(key, unit_id, value) diff --git a/photoshop/api/action_list.py b/photoshop/api/action_list.py index b8c1e3f8..5299538d 100644 --- a/photoshop/api/action_list.py +++ b/photoshop/api/action_list.py @@ -4,11 +4,20 @@ """ + # Import local modules +from os import PathLike +from typing import TYPE_CHECKING + from photoshop.api._core import Photoshop +from photoshop.api.action_reference import ActionReference +from photoshop.api.base_action import BaseAction + +if TYPE_CHECKING: + from photoshop.api.action_descriptor import ActionDescriptor -class ActionList(Photoshop): +class ActionList(BaseAction): """The list of commands that comprise an Action. (such as an Action created using the Actions palette in the Adobe Photoshop application). @@ -19,51 +28,79 @@ class ActionList(Photoshop): object_name = "ActionList" - def __init__(self, parent=None): + def __init__(self, parent: Photoshop | None = None) -> None: super().__init__(parent=parent) self._flag_as_method( - "getBoolean", - "getClass", - "getData", - "getDouble", - "getEnumerationType", - "getEnumerationValue", - "getInteger", - "getLargeInteger", - "getList", - "getObjectType", + "putBoolean", + "putClass", + "putData", + "putDouble", + "putEnumerated", + "putInteger", + "putLargeInteger", + "putList", + "putObject", + "putPath", + "putReference", + "putString", + "putUnitDouble", + "toSteadm", ) - @property - def count(self): - return self.app.count + def putBoolean(self, value: bool) -> None: + """Sets the value for a key whose type is boolean.""" + self.app.putBoolean(value) + + def putClass(self, value: int) -> None: + """Sets the value for a key whose type is class.""" + self.app.putClass(value) + + def putData(self, value: str) -> None: + """Puts raw byte data as a string value.""" + self.app.putData(value) + + def putDouble(self, value: float) -> None: + """Sets the value for a key whose type is double.""" + self.app.putDouble(value) - def getBoolean(self, index): - return self.app.getBoolean(index) + def putEnumerated(self, enum_type: int, value: int) -> None: + """Sets the enumeration type and value for a key.""" + self.app.putEnumerated(enum_type, value) - def getClass(self, index): - return self.app.getClass(index) + def putInteger(self, value: int) -> None: + """Sets the value for a key whose type is integer.""" + self.app.putInteger(value) - def getData(self, index): - return self.app.getData(index) + def putLargeInteger(self, value: int) -> None: + """Sets the value for a key whose type is large integer.""" + self.app.putLargeInteger(value) - def getDouble(self, index): - return self.app.getDouble(index) + def putList(self, value: "ActionList") -> None: + """Sets the value for a key whose type is an ActionList object.""" + self.app.putList(value) - def getEnumerationType(self, index): - return self.app.getEnumerationType(index) + def putObject(self, class_id: int, value: "ActionDescriptor") -> None: + """Sets the value for a key whose type is an object.""" + self.app.putObject(class_id, value) - def getEnumerationValue(self, index): - return self.app.getEnumerationValue(index) + def putPath(self, value: str | PathLike[str]) -> None: + """Sets the value for a key whose type is path.""" + self.app.putPath(str(value)) - def getInteger(self, index): - return self.app.getInteger(index) + def putReference(self, value: ActionReference) -> None: + """Sets the value for a key whose type is an object reference.""" + self.app.putReference(value) - def getLargeInteger(self, index): - return self.app.getLargeInteger(index) + def putString(self, value: str) -> None: + """Sets the value for a key whose type is string.""" + self.app.putString(value) - def getList(self, index): - return self.app.getList(index) + def putUnitDouble(self, unit_id: int, value: float) -> None: + """Sets the value for a key whose type is a unit value formatted as + double.""" + self.app.putUnitDouble(unit_id, value) - def getObjectType(self, index): - return self.app.getObjectType(index) + def toStream(self) -> str: + """Gets the entire descriptor as as stream of bytes, + for writing to disk.""" + return self.app.toSteadm() diff --git a/photoshop/api/action_reference.py b/photoshop/api/action_reference.py index 2e72e3a4..f73efcd2 100644 --- a/photoshop/api/action_reference.py +++ b/photoshop/api/action_reference.py @@ -8,6 +8,7 @@ with an ActionDescriptor. """ + # Import local modules from photoshop.api._core import Photoshop from photoshop.api.enumerations import ReferenceFormType @@ -23,7 +24,7 @@ class ActionReference(Photoshop): object_name = "ActionReference" - def __init__(self, parent=None): + def __init__(self, parent: Photoshop | None = None) -> None: super().__init__(parent=parent) self._flag_as_method( "getContainer", @@ -33,6 +34,9 @@ def __init__(self, parent=None): "getForm", "getIdentifier", "getIndex", + "getName", + "getOffset", + "getProperty", "putName", "putClass", "putEnumerated", @@ -42,10 +46,10 @@ def __init__(self, parent=None): "putProperty", ) - def getContainer(self): + def getContainer(self) -> "ActionReference": return self.app.getContainer() - def getDesiredClass(self): + def getDesiredClass(self) -> int: return self.app.getDesiredClass() def getEnumeratedType(self) -> int: @@ -67,25 +71,37 @@ def getIndex(self) -> int: """Gets the index value for a reference in a list or array,""" return self.app.getIndex() - def putName(self, key, value): - return self.app.putName(key, value) + def getName(self) -> str: + """Gets the name of a reference.""" + return self.app.getName() + + def getOffset(self) -> int: + """Gets the offset of the object's index value.""" + return self.app.getOffset() + + def getProperty(self) -> int: + """Gets the property ID value.""" + return self.app.getProperty() + + def putName(self, key: int, value: str) -> None: + self.app.putName(key, value) - def putClass(self, value): - return self.app.putClass(value) + def putClass(self, value: int) -> None: + self.app.putClass(value) - def putEnumerated(self, desired_class, enum_type, value): + def putEnumerated(self, desired_class: int, enum_type: int, value: int) -> None: """Puts an enumeration type and ID into a reference along with the desired class for the reference.""" - return self.app.putEnumerated(desired_class, enum_type, value) + self.app.putEnumerated(desired_class, enum_type, value) - def putIdentifier(self, desired_class, value): - return self.app.putIdentifier(desired_class, value) + def putIdentifier(self, desired_class: int, value: int) -> None: + self.app.putIdentifier(desired_class, value) - def putIndex(self, desired_class, value): - return self.app.putIndex(desired_class, value) + def putIndex(self, desired_class: int, value: int) -> None: + self.app.putIndex(desired_class, value) - def putOffset(self, desired_class, value): - return self.app.putOffset(desired_class, value) + def putOffset(self, desired_class: int, value: int) -> None: + self.app.putOffset(desired_class, value) - def putProperty(self, desired_class, value): - return self.app.putProperty(desired_class, value) + def putProperty(self, desired_class: int, value: int) -> None: + self.app.putProperty(desired_class, value) diff --git a/photoshop/api/application.py b/photoshop/api/application.py index 0df16cfa..96970e2d 100644 --- a/photoshop/api/application.py +++ b/photoshop/api/application.py @@ -10,16 +10,15 @@ app.documents.add(800, 600, 72, "docRef") """ + # Import built-in modules import os -from pathlib import Path import time -from typing import List -from typing import Optional -from typing import Union # Import third-party modules from _ctypes import COMError +from pathlib import Path +from typing import Any # Import local modules from photoshop.api._artlayer import ArtLayer @@ -31,8 +30,9 @@ from photoshop.api._notifiers import Notifiers from photoshop.api._preferences import Preferences from photoshop.api._text_fonts import TextFonts -from photoshop.api.enumerations import DialogModes -from photoshop.api.enumerations import PurgeTarget +from photoshop.api.action_descriptor import ActionDescriptor +from photoshop.api.batch_options import BatchOptions +from photoshop.api.enumerations import DialogModes, PurgeTarget from photoshop.api.errors import PhotoshopPythonAPIError from photoshop.api.solid_color import SolidColor @@ -45,7 +45,7 @@ class Application(Photoshop): """ - def __init__(self, version: Optional[str] = None): + def __init__(self, version: str | None = None) -> None: super().__init__(ps_version=version) self._flag_as_method( "batch", @@ -91,7 +91,7 @@ def activeDocument(self) -> Document: return Document(self.app.activeDocument) @activeDocument.setter - def activeDocument(self, document: Document): + def activeDocument(self, document: Document) -> None: self.app.activeDocument = document @property @@ -100,7 +100,7 @@ def backgroundColor(self) -> SolidColor: return SolidColor(self.app.backgroundColor) @backgroundColor.setter - def backgroundColor(self, color: SolidColor): + def backgroundColor(self, color: SolidColor) -> None: """Sets the default background color and color style for documents. Args: @@ -123,7 +123,7 @@ def colorSettings(self) -> str: return self.app.colorSettings @colorSettings.setter - def colorSettings(self, settings: str): + def colorSettings(self, settings: str) -> None: """Sets the currently selected color settings profile. Args: @@ -133,7 +133,9 @@ def colorSettings(self, settings: str): try: self.doJavaScript(f'app.colorSettings="{settings}"') except COMError as e: - raise PhotoshopPythonAPIError(f"Invalid color profile provided: '{settings}'") from e + raise PhotoshopPythonAPIError( + f"Invalid color profile provided: '{settings}'" + ) from e @property def currentTool(self) -> str: @@ -141,7 +143,7 @@ def currentTool(self) -> str: return self.app.currentTool @currentTool.setter - def currentTool(self, tool_name: str): + def currentTool(self, tool_name: str) -> None: """Sets the currently selected tool. Args: @@ -157,7 +159,7 @@ def displayDialogs(self) -> DialogModes: return DialogModes(self.app.displayDialogs) @displayDialogs.setter - def displayDialogs(self, dialog_mode: DialogModes): + def displayDialogs(self, dialog_mode: DialogModes) -> None: """The dialog mode for the document, which indicates whether Photoshop displays dialogs when the script runs. """ @@ -185,7 +187,7 @@ def foregroundColor(self) -> SolidColor: return SolidColor(parent=self.app.foregroundColor) @foregroundColor.setter - def foregroundColor(self, color: SolidColor): + def foregroundColor(self, color: SolidColor) -> None: """Set the `foregroundColor`. Args: @@ -205,7 +207,7 @@ def locale(self) -> str: return self.app.locale @property - def macintoshFileTypes(self) -> List[str]: + def macintoshFileTypes(self) -> tuple[str]: """A list of the image file types Photoshop can open.""" return self.app.macintoshFileTypes @@ -230,11 +232,11 @@ def notifiersEnabled(self) -> bool: return self.app.notifiersEnabled @notifiersEnabled.setter - def notifiersEnabled(self, value: bool): + def notifiersEnabled(self, value: bool) -> None: self.app.notifiersEnabled = value @property - def parent(self): + def parent(self) -> object: """The object’s container.""" return self.app.parent @@ -244,16 +246,16 @@ def path(self) -> Path: return Path(self.app.path) @property - def playbackDisplayDialogs(self): + def playbackDisplayDialogs(self) -> DialogModes: return self.doJavaScript("app.playbackDisplayDialogs") @property - def playbackParameters(self): + def playbackParameters(self) -> ActionDescriptor: """Stores and retrieves parameters used as part of a recorded action.""" return self.app.playbackParameters @playbackParameters.setter - def playbackParameters(self, value): + def playbackParameters(self, value: ActionDescriptor) -> None: self.app.playbackParameters = value @property @@ -265,31 +267,37 @@ def preferencesFolder(self) -> Path: return Path(self.app.preferencesFolder) @property - def recentFiles(self): - return self.app.recentFiles + def recentFiles(self) -> list[Path]: + return [Path(pth) for pth in self.app.recentFiles] @property - def scriptingBuildDate(self): + def scriptingBuildDate(self) -> str: return self.app.scriptingBuildDate @property - def scriptingVersion(self): + def scriptingVersion(self) -> str: return self.app.scriptingVersion @property - def systemInformation(self): + def systemInformation(self) -> str: return self.app.systemInformation @property - def version(self): + def version(self) -> str: return self.app.version @property - def windowsFileTypes(self): + def windowsFileTypes(self) -> tuple[str, ...]: return self.app.windowsFileTypes # Methods. - def batch(self, files, actionName, actionSet, options): + def batch( + self, + files: list[str], + actionName: str, + actionSet: str, + options: BatchOptions, + ) -> None: """Runs the batch automation routine. Similar to the **File** > **Automate** > **Batch** command. @@ -297,30 +305,25 @@ def batch(self, files, actionName, actionSet, options): """ self.app.batch(files, actionName, actionSet, options) - def beep(self): + def beep(self) -> None: """Causes a "beep" sound.""" - return self.eval_javascript("app.beep()") + self.eval_javascript("app.beep()") - def bringToFront(self): - return self.eval_javascript("app.bringToFront()") + def bringToFront(self) -> None: + self.eval_javascript("app.bringToFront()") - def changeProgressText(self, text): + def changeProgressText(self, text: str) -> None: """Changes the text that appears in the progress window.""" self.eval_javascript(f"app.changeProgressText('{text}')") - def charIDToTypeID(self, char_id): + def charIDToTypeID(self, char_id: str) -> int: return self.app.charIDToTypeID(char_id) - @staticmethod - def compareWithNumbers(first, second): - return first > second - - def doAction(self, action, action_from="Default Actions"): + def doAction(self, action: str, action_from: str = "Default Actions") -> None: """Plays the specified action from the Actions palette.""" self.app.doAction(action, action_from) - return True - def doForcedProgress(self, title, javascript): + def doForcedProgress(self, title: str, javascript: str) -> None: script = "app.doForcedProgress('{}', '{}')".format( title, javascript, @@ -329,7 +332,7 @@ def doForcedProgress(self, title, javascript): # Ensure the script execute success. time.sleep(1) - def doProgress(self, title, javascript): + def doProgress(self, title: str, javascript: str) -> None: """Performs a task with a progress bar. Other progress APIs must be called periodically to update the progress bar and allow cancelling. @@ -346,7 +349,9 @@ def doProgress(self, title, javascript): # Ensure the script execute success. time.sleep(1) - def doProgressSegmentTask(self, segmentLength, done, total, javascript): + def doProgressSegmentTask( + self, segmentLength: int, done: int, total: int, javascript: str + ) -> None: script = "app.doProgressSegmentTask({}, {}, {}, '{}');".format( segmentLength, done, @@ -357,7 +362,7 @@ def doProgressSegmentTask(self, segmentLength, done, total, javascript): # Ensure the script execute success. time.sleep(1) - def doProgressSubTask(self, index, limit, javascript): + def doProgressSubTask(self, index: int, limit: int, javascript: str) -> None: script = "app.doProgressSubTask({}, {}, '{}');".format( index, limit, @@ -367,27 +372,34 @@ def doProgressSubTask(self, index, limit, javascript): # Ensure the script execute success. time.sleep(1) - def doProgressTask(self, index, javascript): + def doProgressTask(self, taskLength: float, javascript: str) -> None: """Sections off a portion of the unused progress bar for execution of a subtask. Returns false on cancel. """ - script = f"app.doProgressTask({index}, '{javascript}');" + script = f"app.doProgressTask({taskLength}, '{javascript}');" self.eval_javascript(script) # Ensure the script execute success. time.sleep(1) - def eraseCustomOptions(self, key): + def eraseCustomOptions(self, key: str) -> None: """Removes the specified user objects from the Photoshop registry.""" self.app.eraseCustomOptions(key) - def executeAction(self, event_id, descriptor, display_dialogs=2): - return self.app.executeAction(event_id, descriptor, display_dialogs) + def executeAction( + self, + event_id: int, + descriptor: ActionDescriptor, + display_dialogs: DialogModes = DialogModes.DisplayNoDialogs, + ) -> ActionDescriptor: + return ActionDescriptor( + self.app.executeAction(event_id, descriptor, display_dialogs) + ) - def executeActionGet(self, reference): - return self.app.executeActionGet(reference) + def executeActionGet(self, reference: ActionDescriptor) -> ActionDescriptor: + return ActionDescriptor(self.app.executeActionGet(reference)) - def featureEnabled(self, name): + def featureEnabled(self, name: str) -> bool: """Determines whether the feature specified by name is enabled. @@ -401,37 +413,41 @@ def featureEnabled(self, name): """ return self.app.featureEnabled(name) - def getCustomOptions(self, key): + def getCustomOptions(self, key: str) -> ActionDescriptor: """Retrieves user objects in the Photoshop registry for the ID with value key.""" - return self.app.getCustomOptions(key) + return ActionDescriptor(self.app.getCustomOptions(key)) def open( self, - document_file_path, - document_type: str = None, + document_file_path: str | os.PathLike[str], + document_type: str | None = None, as_smart_object: bool = False, ) -> Document: - document = self.app.open(document_file_path, document_type, as_smart_object) + document = self.app.open( + str(document_file_path), document_type, as_smart_object + ) if not as_smart_object: return Document(document) return document - def load(self, document_file_path: Union[str, os.PathLike]) -> Document: + def load(self, document_file_path: str | os.PathLike[str]) -> Document: """Loads a supported Photoshop document.""" self.app.load(str(document_file_path)) return self.activeDocument - def doJavaScript(self, javascript, Arguments=None, ExecutionMode=None): + def doJavaScript( + self, javascript: str, Arguments: Any = None, ExecutionMode: Any = None + ): return self.app.doJavaScript(javascript, Arguments, ExecutionMode) def isQuicktimeAvailable(self) -> bool: return self.app.isQuicktimeAvailable - def openDialog(self): - return self.app.openDialog() + def openDialog(self) -> list[Path]: + return [Path(pth) for pth in self.app.openDialog()] - def purge(self, target: PurgeTarget): + def purge(self, target: PurgeTarget) -> None: """Purges one or more caches. Args: @@ -444,10 +460,12 @@ def purge(self, target: PurgeTarget): """ self.app.purge(target) - def putCustomOptions(self, key, custom_object, persistent): + def putCustomOptions( + self, key: str, custom_object: ActionDescriptor, persistent: bool + ) -> None: self.app.putCustomOptions(key, custom_object, persistent) - def refresh(self): + def refresh(self) -> None: """Pauses the script while the application refreshes. Ues to slow down execution and show the results to the user as the @@ -458,42 +476,38 @@ def refresh(self): """ self.app.refresh() - def refreshFonts(self): + def refreshFonts(self) -> None: """Force the font list to get refreshed.""" - return self.eval_javascript("app.refreshFonts();") + self.eval_javascript("app.refreshFonts();") - def runMenuItem(self, menu_id): + def runMenuItem(self, menu_id: int) -> None: """Run a menu item given the menu ID.""" - return self.eval_javascript( + self.eval_javascript( f"app.runMenuItem({menu_id})", ) - def showColorPicker(self): + def showColorPicker(self) -> str: """Returns false if dialog is cancelled, true otherwise.""" return self.eval_javascript("app.showColorPicker();") - def stringIDToTypeID(self, string_id): + def stringIDToTypeID(self, string_id: str) -> int: return self.app.stringIDToTypeID(string_id) - def togglePalettes(self): + def togglePalettes(self) -> None: """Toggle palette visibility.""" - return self.doJavaScript("app.togglePalettes()") + self.doJavaScript("app.togglePalettes()") - def toolSupportsBrushes(self, tool): + def toolSupportsBrushes(self, tool: str) -> bool: return self.app.toolSupportsBrushes(tool) - def toolSupportsBrushPresets(self, tool): + def toolSupportsBrushPresets(self, tool: str) -> bool: return self.app.toolSupportsPresets(tool) - @staticmethod - def system(command): - os.system(command) - def typeIDToStringID(self, type_id: int) -> str: return self.app.typeIDToStringID(type_id) def typeIDToCharID(self, type_id: int) -> str: return self.app.typeIDToCharID(type_id) - def updateProgress(self, done, total): + def updateProgress(self, done: int, total: int) -> None: self.eval_javascript(f"app.updateProgress({done}, {total})") diff --git a/photoshop/api/base_action.py b/photoshop/api/base_action.py new file mode 100644 index 00000000..3f1f71c1 --- /dev/null +++ b/photoshop/api/base_action.py @@ -0,0 +1,141 @@ +from pathlib import Path +from typing import TYPE_CHECKING + +from photoshop.api._core import Photoshop +from photoshop.api.action_reference import ActionReference +from photoshop.api.enumerations import DescValueType + +if TYPE_CHECKING: + from photoshop.api.action_descriptor import ActionDescriptor + from photoshop.api.action_list import ActionList + + +class BaseAction(Photoshop): + def __init__(self, parent: Photoshop | None = None) -> None: + super().__init__(parent=parent) + self._flag_as_method( + "clear", + "getBoolean", + "getClass", + "getData", + "getDouble", + "getEnumerationType", + "getEnumerationValue", + "getInteger", + "getKey", + "getLargeInteger", + "getList", + "getObjectType", + "getObjectValue", + "getPath", + "getReference", + "getString", + "getType", + "getUnitDoubleType", + "getUnitDoubleValue", + ) + + @property + def count(self) -> int: + """The number of keys contained in the descriptor.""" + return self.app.count + + def clear(self) -> None: + """Clears the descriptor.""" + self.app.clear() + + def getBoolean(self, key: int) -> bool: + """Gets the text_font of a key of type boolean. + + Args: + key (str): key of type boolean. + + Returns: + bool: The text_font of a key of type boolean. + + """ + return self.app.getBoolean(key) + + def getClass(self, key: int) -> int: + """Gets the text_font of a key of type class. + + Args: + key (str): The key of type class. + + Returns: + int: The text_font of a key of type class. + + """ + return self.app.getClass(key) + + def getData(self, key: int) -> str: + """Gets raw byte data as a string value.""" + return self.app.getData(key) + + def getDouble(self, key: int) -> float: + """Gets the value of a key of type double.""" + return self.app.getDouble(key) + + def getEnumerationType(self, index: int) -> int: + """Gets the enumeration type of a key.""" + return self.app.getEnumerationType(index) + + def getEnumerationValue(self, index: int) -> int: + """Gets the enumeration value of a key.""" + return self.app.getEnumerationValue(index) + + def getInteger(self, index: int) -> int: + """Gets the value of a key of type integer.""" + return self.app.getInteger(index) + + def getKey(self, index: int) -> int: + """Gets the ID of the key provided by index.""" + return self.app.getKey(index) + + def getLargeInteger(self, index: int) -> int: + """Gets the value of a key of type large integer.""" + return self.app.getLargeInteger(index) + + def getList(self, index: int) -> "ActionList": + """Gets the value of a key of type list.""" + from photoshop.api.action_list import ActionList + + return ActionList(self.app.getList(index)) + + def getObjectType(self, key: int) -> int: + """Gets the class ID of an object in a key of type object.""" + return self.app.getObjectType(key) + + def getObjectValue(self, key: int) -> "ActionDescriptor": + """Get the class ID of an object in a key of type object.""" + from .action_descriptor import ActionDescriptor + + return ActionDescriptor(self.app.getObjectValue(key)) + + def getPath(self, key: int) -> Path: + """Gets the value of a key of type File.""" + return Path(self.app.getPath(key)) + + def getReference(self, key: int) -> ActionReference: + """Gets the value of a key of type.""" + return ActionReference(self.app.getReference(key)) + + def getString(self, key: int) -> str: + """Gets the value of a key of type.""" + return self.app.getString(key) + + def getType(self, key: int) -> DescValueType: + """Gets the type of a key.""" + return DescValueType(self.app.getType(key)) + + def getUnitDoubleType(self, key: int) -> int: + """Gets the unit type of a key of type UnitDouble.""" + return self.app.getUnitDoubleType(key) + + def getUnitDoubleValue(self, key: int) -> float: + """Gets the unit type of a key of type UnitDouble.""" + return self.app.getUnitDoubleValue(key) + + def hasKey(self, key: int) -> bool: + """Checks whether the descriptor contains the provided key.""" + return self.app.hasKey(key) diff --git a/photoshop/api/batch_options.py b/photoshop/api/batch_options.py index 87156b24..f679241a 100644 --- a/photoshop/api/batch_options.py +++ b/photoshop/api/batch_options.py @@ -1,49 +1,57 @@ # https://theiviaxx.github.io/photoshop-docs/Photoshop/BatchOptions.html # Import local modules +from os import PathLike +from pathlib import Path +from typing import Sequence + from photoshop.api._core import Photoshop +from photoshop.api.enumerations import BatchDestinationType, FileNamingType class BatchOptions(Photoshop): object_name = "BatchOptions" - def __init__(self): - super().__init__() - @property - def destination(self): + def destination(self) -> BatchDestinationType: """The type of destination for the processed files.""" - return self.app.destination + return BatchDestinationType(self.app.destination) @destination.setter - def destination(self, value): + def destination(self, value: BatchDestinationType) -> None: self.app.destination = value @property - def destinationFolder(self): - """The folder location for the processed files. Valid only when ‘destination’ = folder.""" - return self.app.destinationFolder + def destinationFolder(self) -> Path | None: + """The folder location for the processed files. Valid only when ‘destination’ == BatchDestinationType.FOLDER.""" + destination = self.app.destinationFolder + if destination: + return Path(self.app.destinationFolder) + return None @destinationFolder.setter - def destinationFolder(self, path): - self.app.destinationFolder = path + def destinationFolder(self, path: str | PathLike[str]) -> None: + self.app.destinationFolder = str(path) @property - def errorFile(self): + def errorFile(self) -> Path | None: """The file in which to log errors encountered. To display errors on the screen and stop batch processing when errors occur, leave blank.""" - return self.app.errorFile + err_file = self.app.errorFile + if err_file: + return Path(self.app.errorFile) + return None @errorFile.setter - def errorFile(self, file_path): - self.app.errorFile = file_path + def errorFile(self, file_path: str | PathLike[str] | None) -> None: + self.app.errorFile = str(file_path) if file_path else None @property - def fileNaming(self) -> list: + def fileNaming(self) -> list[FileNamingType]: """A list of file naming options. Maximum: 6.""" - return self.app.fileNaming + return [FileNamingType(file_naming) for file_naming in self.app.fileNaming] @fileNaming.setter - def fileNaming(self, file_naming: list): + def fileNaming(self, file_naming: Sequence[FileNamingType]) -> None: self.app.fileNaming = file_naming @property @@ -52,7 +60,7 @@ def macintoshCompatible(self) -> bool: return self.app.macintoshCompatible @macintoshCompatible.setter - def macintoshCompatible(self, value: bool): + def macintoshCompatible(self, value: bool) -> None: self.app.macintoshCompatible = value @property @@ -61,7 +69,7 @@ def overrideOpen(self) -> bool: return self.app.overrideOpen @overrideOpen.setter - def overrideOpen(self, value: bool): + def overrideOpen(self, value: bool) -> None: self.app.overrideOpen = value @property @@ -70,7 +78,7 @@ def overrideSave(self) -> bool: return self.app.overrideSave @overrideSave.setter - def overrideSave(self, value: bool): + def overrideSave(self, value: bool) -> None: self.app.overrideSave = value @property @@ -79,7 +87,7 @@ def startingSerial(self) -> int: return self.app.startingSerial @startingSerial.setter - def startingSerial(self, value: int): + def startingSerial(self, value: int) -> None: self.app.startingSerial = value @property @@ -88,7 +96,7 @@ def suppressOpen(self) -> bool: return self.app.suppressOpen @suppressOpen.setter - def suppressOpen(self, value: bool): + def suppressOpen(self, value: bool) -> None: self.app.suppressOpen = value @property @@ -97,7 +105,7 @@ def suppressProfile(self) -> bool: return self.app.suppressProfile @suppressProfile.setter - def suppressProfile(self, value: bool): + def suppressProfile(self, value: bool) -> None: self.app.suppressProfile = value @property @@ -106,7 +114,7 @@ def unixCompatible(self) -> bool: return self.app.unixCompatible @unixCompatible.setter - def unixCompatible(self, value: bool): + def unixCompatible(self, value: bool) -> None: self.app.unixCompatible = value @property @@ -115,5 +123,5 @@ def windowsCompatible(self) -> bool: return self.app.windowsCompatible @windowsCompatible.setter - def windowsCompatible(self, value: bool): + def windowsCompatible(self, value: bool) -> None: self.app.windowsCompatible = value diff --git a/photoshop/api/collections.py b/photoshop/api/collections.py new file mode 100644 index 00000000..1a32f837 --- /dev/null +++ b/photoshop/api/collections.py @@ -0,0 +1,91 @@ +from typing import Generic, Iterator, Protocol, TypeVar + +from comtypes import ArgumentError + +from photoshop.api._core import Photoshop +from photoshop.api.errors import PhotoshopPythonAPIError + + +class _PhotoshopObject(Protocol): + def __init__(self, parent: Photoshop | None = None) -> None: ... + + +class NamedPhotoshopObject(_PhotoshopObject, Protocol): + @property + def name(self) -> str: ... + + +T = TypeVar("T", bound=_PhotoshopObject) +N = TypeVar("N", bound=NamedPhotoshopObject) +G = TypeVar("G", bound=int | str) + + +class BaseCollection(Photoshop, Generic[T, G]): + """A collection of Photoshop objects.""" + + def __init__(self, type: type[T], parent: Photoshop | None = None) -> None: + super().__init__(parent=parent) + self.type = type + self._flag_as_method("item") + + @property + def length(self) -> int: + return len(list(self.app)) + + def __len__(self) -> int: + return self.length + + def __getitem__(self, key: G) -> T: + try: + return self.type(self.app[key]) + except ArgumentError as exc: + raise PhotoshopPythonAPIError( + f"Couldn't find an item with key '{key}' in {type(self)}" + ) from exc + + def __iter__(self) -> Iterator[T]: + for item in self.app: + yield self.type(item) + + def item(self, index: int) -> T: + return self.type(self.app.item(index)) + + def getByIndex(self, index: int) -> T: + for idx, item in enumerate(self.app): + if idx == index: + return self.type(item) + raise IndexError(f"Index {index} is out of range in {type(self)}") + + +class CollectionWithAdd(BaseCollection[T, G]): + """Collection that has a add method that takes no arguments.""" + + def __init__(self, type: type[T], parent: Photoshop | None = None) -> None: + super().__init__(type, parent) + self._flag_as_method("add") + + def add(self) -> T: + return self.type(self.app.add()) + + +class CollectionOfNamedObjects(BaseCollection[N, G]): + """A collection of named Photoshop objects.""" + + def getByName(self, name: str) -> N | None: + """Get the first element in the collection with the provided name.""" + for item in self.app: + if item.name == name: + return self.type(item) + return None + + +class CollectionOfRemovables(BaseCollection[T, G]): + """A collection of removable Photoshop objects.""" + + def __init__(self, type: type[T], parent: Photoshop | None = None) -> None: + super().__init__(type, parent) + self._flag_as_method("removeAll") + + def removeAll(self) -> None: + """Deletes all items.""" + self.app.removeAll() diff --git a/photoshop/api/colors/cmyk.py b/photoshop/api/colors/cmyk.py index dbe260b8..585b7027 100644 --- a/photoshop/api/colors/cmyk.py +++ b/photoshop/api/colors/cmyk.py @@ -9,41 +9,41 @@ class CMYKColor(Photoshop): object_name = "CMYKColor" - def __init__(self, parent): + def __init__(self, parent: Photoshop | None = None) -> None: super().__init__(parent=parent) @property - def black(self) -> int: + def black(self) -> float: """The black color value. Range: 0.0 to 100.0.""" - return round(self.app.black) + return self.app.black @black.setter - def black(self, value: int): + def black(self, value: float) -> None: self.app.black = value @property - def cyan(self) -> int: + def cyan(self) -> float: """The cyan color value. Range: 0.0 to 100.0.""" - return round(self.app.cyan) + return self.app.cyan @cyan.setter - def cyan(self, value: int): + def cyan(self, value: float) -> None: self.app.cyan = value @property - def magenta(self) -> int: + def magenta(self) -> float: """The magenta color value. Range: 0.0 to 100.0.""" - return round(self.app.magenta) + return self.app.magenta @magenta.setter - def magenta(self, value: int): + def magenta(self, value: float) -> None: self.app.magenta = value @property - def yellow(self) -> int: + def yellow(self) -> float: """The yellow color value. Range: 0.0 to 100.0.""" - return round(self.app.yellow) + return self.app.yellow @yellow.setter - def yellow(self, value: int): + def yellow(self, value: float) -> None: self.app.yellow = value diff --git a/photoshop/api/colors/gray.py b/photoshop/api/colors/gray.py index b2321a02..155a9db8 100644 --- a/photoshop/api/colors/gray.py +++ b/photoshop/api/colors/gray.py @@ -9,7 +9,7 @@ class GrayColor(Photoshop): object_name = "GrayColor" - def __init__(self, parent): + def __init__(self, parent: Photoshop | None = None) -> None: super().__init__(parent=parent) @property @@ -18,6 +18,6 @@ def gray(self) -> float: return self.app.gray @gray.setter - def gray(self, value: float): + def gray(self, value: float) -> None: """The gray value.""" self.app.gray = value diff --git a/photoshop/api/colors/hsb.py b/photoshop/api/colors/hsb.py index 76ead8c8..9e28c900 100644 --- a/photoshop/api/colors/hsb.py +++ b/photoshop/api/colors/hsb.py @@ -9,30 +9,30 @@ class HSBColor(Photoshop): object_name = "HSBColor" - def __init__(self, parent): + def __init__(self, parent: Photoshop | None = None) -> None: super().__init__(parent=parent) @property - def brightness(self): - return round(self.app.brightness) + def brightness(self) -> float: + return self.app.brightness @brightness.setter - def brightness(self, value): + def brightness(self, value: float) -> None: self.app.brightness = value @property - def saturation(self): - return round(self.app.saturation) + def saturation(self) -> float: + return self.app.saturation @saturation.setter - def saturation(self, value): + def saturation(self, value: float) -> None: self.app.saturation = value @property - def hue(self): + def hue(self) -> float: """The hue value. Range: 0.0 to 360.0.""" - return round(self.app.hue) + return self.app.hue @hue.setter - def hue(self, value): + def hue(self, value: float) -> None: self.app.hue = value diff --git a/photoshop/api/colors/lab.py b/photoshop/api/colors/lab.py index 282493d9..a0b1b866 100644 --- a/photoshop/api/colors/lab.py +++ b/photoshop/api/colors/lab.py @@ -7,29 +7,29 @@ class LabColor(Photoshop): object_name = "LabColor" - def __init__(self, parent): + def __init__(self, parent: Photoshop | None = None) -> None: super().__init__(parent=parent) @property - def A(self): - return round(self.app.A) + def A(self) -> float: + return self.app.A @A.setter - def A(self, value): + def A(self, value: float) -> None: self.app.A = value @property - def B(self): - return round(self.app.B) + def B(self) -> float: + return self.app.B @B.setter - def B(self, value): + def B(self, value: float) -> None: self.app.B = value @property - def L(self): - return round(self.app.L) + def L(self) -> float: + return self.app.L @L.setter - def L(self, value): + def L(self, value: float) -> None: self.app.L = value diff --git a/photoshop/api/colors/rgb.py b/photoshop/api/colors/rgb.py index 2d6475bd..d1f2575f 100644 --- a/photoshop/api/colors/rgb.py +++ b/photoshop/api/colors/rgb.py @@ -7,43 +7,43 @@ class RGBColor(Photoshop): object_name = "RGBColor" - def __init__(self, parent): + def __init__(self, parent: Photoshop | None = None) -> None: super().__init__(parent=parent) self.blue = self.app.blue self.green = self.app.green self.red = self.app.red @property - def blue(self) -> int: - return round(self.app.blue) + def blue(self) -> float: + return self.app.blue @blue.setter - def blue(self, value: int): + def blue(self, value: float) -> None: self.app.blue = value @property - def green(self) -> int: - return round(self.app.green) + def green(self) -> float: + return self.app.green @green.setter - def green(self, value: int): + def green(self, value: float) -> None: self.app.green = value @property - def red(self) -> int: - return round(self.app.red) + def red(self) -> float: + return self.app.red @red.setter - def red(self, value: int): + def red(self, value: float) -> None: self.app.red = value @property - def hexValue(self): + def hexValue(self) -> str: return self.app.hexValue @hexValue.setter - def hexValue(self, value): + def hexValue(self, value: str) -> None: self.app.hexValue = value - def __str__(self): + def __str__(self) -> str: return f"[red: {self.red}, green:{self.green}, blue:{self.blue})]" diff --git a/photoshop/api/enumerations.py b/photoshop/api/enumerations.py index f1b40a3d..3ee97361 100644 --- a/photoshop/api/enumerations.py +++ b/photoshop/api/enumerations.py @@ -1,6 +1,6 @@ """constants type of enum for Photoshop.""" # Import built-in modules -from enum import IntEnum +from enum import IntEnum, StrEnum class LensType(IntEnum): @@ -383,6 +383,12 @@ class FontPreviewType(IntEnum): FontPreviewSmall = 1 +class FontSize(StrEnum): + Large = "FontSize.LARGE" + Medium = "FontSize.MEDIUM" + Small = "FontSize.SMALL" + + class ForcedColors(IntEnum): BlackWhite = 2 NoForced = 1 @@ -1086,7 +1092,7 @@ class Urgency(IntEnum): Two = 2 -class Wartyle(IntEnum): +class WarpStyle(IntEnum): Arc = 2 ArcLower = 3 ArcUpper = 4 @@ -1256,7 +1262,7 @@ class ZigZagType(IntEnum): "UnderlineType", "Units", "Urgency", - "Wartyle", + "WarpStyle", "WaveType", "WhiteBalanceType", "ZigZagType", diff --git a/photoshop/api/open_options/eps.py b/photoshop/api/open_options/eps.py index fee488c9..090964a3 100644 --- a/photoshop/api/open_options/eps.py +++ b/photoshop/api/open_options/eps.py @@ -1,5 +1,6 @@ # Import local modules from photoshop.api._core import Photoshop +from photoshop.api.enumerations import OpenDocumentMode class EPSOpenOptions(Photoshop): @@ -11,37 +12,53 @@ class EPSOpenOptions(Photoshop): object_name = "EPSOpenOptions" - def __init__(self): + def __init__(self) -> None: super().__init__() @property - def antiAlias(self): + def antiAlias(self) -> bool: return self.app.antiAlias + @antiAlias.setter + def antiAlias(self, value: bool) -> None: + self.app.antiAlias = value + @property - def constrainProportions(self): + def constrainProportions(self) -> bool: return self.app.constrainProportions + @constrainProportions.setter + def constrainProportions(self, value: bool) -> None: + self.app.constrainProportions = value + @property - def height(self): + def height(self) -> float: return self.app.height + @height.setter + def height(self, value: float) -> None: + self.app.height = value + @property - def mode(self): - return self.app.mode + def mode(self) -> OpenDocumentMode: + return OpenDocumentMode(self.app.mode) + + @mode.setter + def mode(self, value: OpenDocumentMode) -> None: + self.app.mode = value @property - def resolution(self): + def resolution(self) -> float: return self.app.resolution - @property - def width(self): - return self.app.width + @resolution.setter + def resolution(self, value: float) -> None: + self.app.resolution = value @property - def embedColorProfile(self): - return self.app.embedColorProfile + def width(self) -> float: + return self.app.width - @embedColorProfile.setter - def embedColorProfile(self, boolean): - self.app.embedColorProfile = boolean + @width.setter + def width(self, value: float) -> None: + self.app.width = value diff --git a/photoshop/api/path_item.py b/photoshop/api/path_item.py new file mode 100644 index 00000000..ae11d8fd --- /dev/null +++ b/photoshop/api/path_item.py @@ -0,0 +1,100 @@ +from typing import Iterator + +from git import TYPE_CHECKING + +from photoshop.api._core import Photoshop +from photoshop.api.enumerations import ColorBlendMode, PathKind, SelectionType, ToolType +from photoshop.api.solid_color import SolidColor +from photoshop.api.sub_path_item import SubPathItem + +if TYPE_CHECKING: + from photoshop.api._document import Document + + +class PathItem(Photoshop): + def __init__(self, parent: Photoshop | None = None) -> None: + super().__init__(parent=parent) + self._flag_as_method( + "deselect", + "duplicate", + "fillPath", + "makeClippingPath", + "makeSelection", + "delete", + "select", + "strokePath", + ) + + @property + def kind(self) -> PathKind: + return PathKind(self.app.kind) + + @kind.setter + def kind(self, value: PathKind) -> None: + self.app.kind = value + + @property + def name(self) -> str: + return self.app.name + + @name.setter + def name(self, value: str) -> None: + self.app.name = value + + @property + def parent(self) -> "Document": + from photoshop.api._document import Document + + return Document(self.app.parent) + + @property + def subPathItems(self) -> Iterator[SubPathItem]: + for sub_path in self.app.subPathItems: + yield SubPathItem(sub_path) + + def deselect(self) -> None: + self.app.deselect() + + def duplicate(self, name: str | None = None) -> None: + name = name if name else f"Duplicate of {self.name}" + return self.app.duplicate(name) + + def fillPath( + self, + fill_color: SolidColor, + mode: ColorBlendMode | None = None, + opacity: float = 100, + preserve_transparency: bool = False, + feather: float = 0, + whole_path: bool = True, + anti_alias: bool = True, + ) -> None: + return self.app.fillPath( + fill_color, + mode, + opacity, + preserve_transparency, + feather, + whole_path, + anti_alias, + ) + + def makeClippingPath(self, flatness: float) -> None: + return self.app.makeClippingPath(flatness) + + def makeSelection( + self, + feather: float = 0, + anti_alias: bool = True, + operation: SelectionType | None = None, + ) -> None: + return self.app.makeSelection(feather, anti_alias, operation) + + def remove(self) -> None: + return self.app.delete() + + def select(self) -> None: + return self.app.select() + + def strokePath(self, tool: ToolType, simulate_pressure: bool = False) -> None: + return self.app.strokePath(tool, simulate_pressure) diff --git a/photoshop/api/path_items.py b/photoshop/api/path_items.py new file mode 100644 index 00000000..626d2dfa --- /dev/null +++ b/photoshop/api/path_items.py @@ -0,0 +1,27 @@ +from typing import TYPE_CHECKING, Sequence + +from photoshop.api._core import Photoshop +from photoshop.api.collections import CollectionOfNamedObjects, CollectionOfRemovables +from photoshop.api.path_item import PathItem +from photoshop.api.sub_path_info import SubPathInfo + +if TYPE_CHECKING: + from photoshop.api._document import Document + + +class PathItems( + CollectionOfRemovables[PathItem, int | str], + CollectionOfNamedObjects[PathItem, int | str], +): + def __init__(self, parent: Photoshop | None = None) -> None: + super().__init__(PathItem, parent) + self._flag_as_method("add") + + def add(self, name: str, entire_path: Sequence[SubPathInfo]) -> PathItem: + return PathItem(self.app.add(name, entire_path)) + + @property + def parent(self) -> "Document": + from photoshop.api._document import Document + + return Document(self.app.parent) diff --git a/photoshop/api/path_point.py b/photoshop/api/path_point.py new file mode 100644 index 00000000..a9504ad8 --- /dev/null +++ b/photoshop/api/path_point.py @@ -0,0 +1,34 @@ +from typing import TYPE_CHECKING + +from photoshop.api._core import Photoshop +from photoshop.api.enumerations import PointKind + +if TYPE_CHECKING: + from photoshop.api.sub_path_item import SubPathItem + + +class PathPoint(Photoshop): + def __init__(self, parent: Photoshop | None = None) -> None: + super().__init__(parent=parent) + + @property + def anchor(self) -> tuple[float, float]: + return self.app.anchor + + @property + def kind(self) -> PointKind: + return PointKind(self.app.kind) + + @property + def leftDirection(self) -> tuple[float, float]: + return self.app.leftDirection + + @property + def parent(self) -> "SubPathItem": + from photoshop.api.sub_path_item import SubPathItem + + return SubPathItem(self.app.parent) + + @property + def rightDirection(self) -> tuple[float, float]: + return self.app.rightDirection diff --git a/photoshop/api/path_point_info.py b/photoshop/api/path_point_info.py new file mode 100644 index 00000000..2920b5ad --- /dev/null +++ b/photoshop/api/path_point_info.py @@ -0,0 +1,73 @@ +from photoshop.api._core import Photoshop +from photoshop.api.enumerations import PointKind + + +class PathPointInfo(Photoshop): + # The COM API somehow uses "object_name" to recognize this as an object of + # that type, which allows instantiating this object in Python and then passing + # it to COM API functions that use objects of that type. + object_name = "PathPointInfo" + + def __init__( + self, + parent: Photoshop | None = None, + anchor: tuple[float, float] | None = None, + left_direction: tuple[float, float] | None = None, + right_direction: tuple[float, float] | None = None, + kind: PointKind | None = None, + ): + super().__init__(parent=parent) + + # Each of anchor, leftDirection and rightDirection have to be set + # for the point to be valid. + if anchor: + self.anchor = anchor + + if left_direction: + self.leftDirection = left_direction + elif anchor: + self.leftDirection = anchor + + if right_direction: + self.rightDirection = right_direction + elif anchor: + self.rightDirection = anchor + + if kind: + self.kind = kind + elif left_direction or right_direction: + self.kind = PointKind.SmoothPoint + elif anchor: + self.kind = PointKind.CornerPoint + + @property + def anchor(self) -> tuple[float, float]: + return self.app.anchor + + @anchor.setter + def anchor(self, value: tuple[float, float]) -> None: + self.app.anchor = value + + @property + def kind(self) -> PointKind: + return PointKind(self.app.kind) + + @kind.setter + def kind(self, value: PointKind) -> None: + self.app.kind = value + + @property + def leftDirection(self) -> tuple[float, float]: + return self.app.leftDirection + + @leftDirection.setter + def leftDirection(self, value: tuple[float, float]) -> None: + self.app.leftDirection = value + + @property + def rightDirection(self) -> tuple[float, float]: + return self.app.rightDirection + + @rightDirection.setter + def rightDirection(self, value: tuple[float, float]) -> None: + self.app.rightDirection = value diff --git a/photoshop/api/protocols.py b/photoshop/api/protocols.py new file mode 100644 index 00000000..a88b88b8 --- /dev/null +++ b/photoshop/api/protocols.py @@ -0,0 +1,31 @@ +from typing import TYPE_CHECKING, Protocol + +if TYPE_CHECKING: + from photoshop.api._document import Document + + +class BaseProtocol(Protocol): + @property + def typename(self) -> str: ... + + +class HistoryState(BaseProtocol, Protocol): + @property + def name(self) -> str: ... + @property + def parent(self) -> "Document": ... + @property + def snapshot(self) -> bool: ... + + +class MeasurementScale(BaseProtocol, Protocol): + logicalLength: float + logicalUnits: str + pixelLength: int + + +class XMPMetadata(BaseProtocol, Protocol): + @property + def parent(self) -> "Document": ... + + rawData: str diff --git a/photoshop/api/save_options/bmp.py b/photoshop/api/save_options/bmp.py index 8a1c1ae8..c7dbf198 100644 --- a/photoshop/api/save_options/bmp.py +++ b/photoshop/api/save_options/bmp.py @@ -2,6 +2,7 @@ # Import local modules from photoshop.api._core import Photoshop +from photoshop.api.enumerations import BMPDepthType, OperatingSystem class BMPSaveOptions(Photoshop): @@ -9,19 +10,51 @@ class BMPSaveOptions(Photoshop): object_name = "BMPSaveOptions" - def __init__(self): + def __init__(self) -> None: super().__init__() @property - def alphaChannels(self): + def alphaChannels(self) -> bool: """State to save the alpha channels.""" return self.app.alphaChannels @alphaChannels.setter - def alphaChannels(self, value): + def alphaChannels(self, value: bool) -> None: """Sets whether to save the alpha channels or not. Args: """ self.app.alphaChannels = value + + @property + def depth(self) -> BMPDepthType: + return BMPDepthType(self.app.depth) + + @depth.setter + def depth(self, value: BMPDepthType) -> None: + self.app.depth = value + + @property + def flipRowOrder(self) -> bool: + return self.app.flipRowOrder + + @flipRowOrder.setter + def flipRowOrder(self, value: bool) -> None: + self.app.flipRowOrder = value + + @property + def osType(self) -> OperatingSystem: + return OperatingSystem(self.app.osType) + + @osType.setter + def osType(self, value: OperatingSystem) -> None: + self.app.osType = value + + @property + def rleCompression(self) -> bool: + return self.app.rleCompression + + @rleCompression.setter + def rleCompression(self, value: bool) -> None: + self.app.rleCompression = value diff --git a/photoshop/api/save_options/eps.py b/photoshop/api/save_options/eps.py index d44aefb6..0561e49e 100644 --- a/photoshop/api/save_options/eps.py +++ b/photoshop/api/save_options/eps.py @@ -1,5 +1,6 @@ # Import local modules from photoshop.api._core import Photoshop +from photoshop.api.enumerations import PreviewType, SaveEncoding class EPSSaveOptions(Photoshop): @@ -11,7 +12,7 @@ class EPSSaveOptions(Photoshop): object_name = "EPSSaveOptions" - def __init__(self): + def __init__(self) -> None: super().__init__() @property @@ -20,56 +21,56 @@ def embedColorProfile(self) -> bool: return self.app.embedColorProfile @embedColorProfile.setter - def embedColorProfile(self, boolean: bool): + def embedColorProfile(self, boolean: bool) -> None: """True to embed the color profile in this document.""" self.app.embedColorProfile = boolean @property - def encoding(self): - return self.app.encoding + def encoding(self) -> SaveEncoding: + return SaveEncoding(self.app.encoding) @encoding.setter - def encoding(self, value: bool): + def encoding(self, value: SaveEncoding) -> None: self.app.encoding = value @property - def halftoneScreen(self): + def halftoneScreen(self) -> bool: return self.app.halftoneScreen @halftoneScreen.setter - def halftoneScreen(self, value: bool): + def halftoneScreen(self, value: bool) -> None: self.app.halftoneScreen = value @property - def interpolation(self): + def interpolation(self) -> bool: return self.app.interpolation @interpolation.setter - def interpolation(self, value: bool): + def interpolation(self, value: bool) -> None: self.app.interpolation = value @property - def preview(self): - return self.app.preview + def preview(self) -> PreviewType: + return PreviewType(self.app.preview) @preview.setter - def preview(self, value: bool): + def preview(self, value: PreviewType) -> None: self.app.preview = value @property - def psColorManagement(self): + def psColorManagement(self) -> bool: return self.app.psColorManagement @psColorManagement.setter - def psColorManagement(self, value: bool): + def psColorManagement(self, value: bool) -> None: self.app.psColorManagement = value @property - def transferFunction(self): + def transferFunction(self) -> bool: return self.app.transferFunction @transferFunction.setter - def transferFunction(self, value: bool): + def transferFunction(self, value: bool) -> None: self.app.transferFunction = value @property @@ -78,17 +79,17 @@ def transparentWhites(self) -> bool: return self.app.transparentWhites @transparentWhites.setter - def transparentWhites(self, value: bool): + def transparentWhites(self, value: bool) -> None: """True to display white areas as transparent""" self.app.transparentWhites = value @property - def vectorData(self): + def vectorData(self) -> bool: """True to include vector data.""" return self.app.vectorData @vectorData.setter - def vectorData(self, value: bool): + def vectorData(self, value: bool) -> None: """True to include vector data. Valid only if the document includes vector data (text). diff --git a/photoshop/api/save_options/gif.py b/photoshop/api/save_options/gif.py index df667305..7a8765c2 100644 --- a/photoshop/api/save_options/gif.py +++ b/photoshop/api/save_options/gif.py @@ -1,5 +1,6 @@ # Import local modules from photoshop.api._core import Photoshop +from photoshop.api.enumerations import DitherType, ForcedColors, MatteType, PaletteType class GIFSaveOptions(Photoshop): @@ -7,77 +8,77 @@ class GIFSaveOptions(Photoshop): object_name = "GIFSaveOptions" - def __init__(self): + def __init__(self) -> None: super().__init__() @property - def colors(self): + def colors(self) -> int: return self.app.color @colors.setter - def colors(self, value): + def colors(self, value: int) -> None: self.app.colors = value @property - def dither(self): - return self.app.dither + def dither(self) -> DitherType: + return DitherType(self.app.dither) @dither.setter - def dither(self, value): + def dither(self, value: DitherType) -> None: self.app.dither = value @property - def ditherAmount(self): + def ditherAmount(self) -> int: return self.app.ditherAmount @ditherAmount.setter - def ditherAmount(self, value): + def ditherAmount(self, value: int) -> None: self.app.ditherAmount = value @property - def forced(self): - return self.app.forced + def forced(self) -> ForcedColors: + return ForcedColors(self.app.forced) @forced.setter - def forced(self, value): + def forced(self, value: ForcedColors) -> None: self.app.forced = value @property - def interlaced(self): + def interlaced(self) -> bool: return self.app.interlaced @interlaced.setter - def interlaced(self, value): + def interlaced(self, value: bool) -> None: self.app.interlaced = value @property - def matte(self): - return self.app.matte + def matte(self) -> MatteType: + return MatteType(self.app.matte) @matte.setter - def matte(self, value): + def matte(self, value: MatteType) -> None: self.app.matte = value @property - def palette(self): - return self.app.palette + def palette(self) -> PaletteType: + return PaletteType(self.app.palette) @palette.setter - def palette(self, value): + def palette(self, value: PaletteType) -> None: self.app.palette = value @property - def preserveExactColors(self): + def preserveExactColors(self) -> bool: return self.app.preserveExactColors @preserveExactColors.setter - def preserveExactColors(self, value): + def preserveExactColors(self, value: bool) -> None: self.app.preserveExactColors = value @property - def transparency(self): + def transparency(self) -> bool: return self.app.transparency @transparency.setter - def transparency(self, value): + def transparency(self, value: bool) -> None: self.app.transparency = value diff --git a/photoshop/api/save_options/jpg.py b/photoshop/api/save_options/jpg.py index b54a3353..3decb05b 100644 --- a/photoshop/api/save_options/jpg.py +++ b/photoshop/api/save_options/jpg.py @@ -1,6 +1,6 @@ # Import local modules from photoshop.api._core import Photoshop -from photoshop.api.enumerations import MatteType +from photoshop.api.enumerations import FormatOptionsType, MatteType class JPEGSaveOptions(Photoshop): @@ -8,51 +8,56 @@ class JPEGSaveOptions(Photoshop): object_name = "JPEGSaveOptions" - def __init__(self, quality=5, embedColorProfile=True, matte=MatteType.NoMatte): + def __init__( + self, + quality: int = 5, + embedColorProfile: bool = True, + matte: MatteType = MatteType.NoMatte, + ): super().__init__() self.quality = quality self.embedColorProfile = embedColorProfile self.matte = matte @property - def quality(self): + def quality(self) -> int: return self.app.quality @quality.setter - def quality(self, value): + def quality(self, value: int) -> None: self.app.quality = value @property - def formatOptions(self): + def formatOptions(self) -> FormatOptionsType: """The download format to use.""" - return self.app.formatOptions + return FormatOptionsType(self.app.formatOptions) @formatOptions.setter - def formatOptions(self, value): + def formatOptions(self, value: FormatOptionsType) -> None: self.app.formatOptions = value @property - def embedColorProfile(self): + def embedColorProfile(self) -> bool: return self.app.embedColorProfile @embedColorProfile.setter - def embedColorProfile(self, value): + def embedColorProfile(self, value: bool) -> None: self.app.embedColorProfile = value @property - def matte(self): + def matte(self) -> MatteType: """The color to use to fill anti-aliased edges adjacent to transparent""" - return self.app.matte + return MatteType(self.app.matte) @matte.setter - def matte(self, value): + def matte(self, value: MatteType) -> None: self.app.matte = value @property - def scans(self): + def scans(self) -> int: return self.app.scans @scans.setter - def scans(self, value): + def scans(self, value: int) -> None: self.app.scans = value diff --git a/photoshop/api/save_options/pdf.py b/photoshop/api/save_options/pdf.py index fdc4fb18..0602164b 100644 --- a/photoshop/api/save_options/pdf.py +++ b/photoshop/api/save_options/pdf.py @@ -6,8 +6,12 @@ # Import local modules from photoshop.api._core import Photoshop -from photoshop.api.enumerations import PDFEncodingType -from photoshop.api.enumerations import PDFResampleType +from photoshop.api.enumerations import ( + PDFCompatibilityType, + PDFEncodingType, + PDFResampleType, + PDFStandardType, +) from photoshop.api.errors import COMError @@ -17,179 +21,234 @@ class PDFSaveOptions(Photoshop): object_name = "PDFSaveOptions" - def __init__(self, **kwargs): + def __init__( + self, + alphaChannels: bool = False, + annotations: bool = True, + colorConversion: bool = False, + convertToEightBit: bool = True, + description: str = "No description.", + destinationProfile: str | None = None, + downSample: PDFResampleType = PDFResampleType.NoResample, + downSampleSize: float | None = None, + downSampleSizeLimit: float | None = None, + embedColorProfile: bool = True, + embedThumbnail: bool = False, + encoding: PDFEncodingType = PDFEncodingType.PDFZip, + jpegQuality: int = 12, + layers: bool = False, + optimizeForWeb: bool = False, + outputCondition: str | None = None, + outputConditionID: str | None = None, + PDFCompatibility: PDFCompatibilityType | None = None, + PDFStandard: PDFStandardType | None = None, + preserveEditing: bool = False, + presetFile: str | None = None, + profileInclusionPolicy: bool | None = None, + registryName: str | None = None, + spotColors: bool | None = None, + tileSize: int | None = None, + view: bool = False, + ): super().__init__() - self.layers = False - self.jpegQuality = 12 - self.alphaChannels = False - self.embedThumbnail = True - self.view = False - self.annotations = True - self.colorConversion = False - self.convertToEightBit = True - self.description = "No description." - self.encoding_types = PDFEncodingType - self.downSample = PDFResampleType.NoResample - self.embedColorProfile = True - if kwargs: - if "encoding" in kwargs: - self.encoding = kwargs.get("encoding", self.encoding_types.PDFJPEG) - for key, value in kwargs.items(): - setattr(self, key, value) + self.alphaChannels = alphaChannels + self.annotations = annotations + self.colorConversion = colorConversion + self.convertToEightBit = convertToEightBit + self.description = description + if destinationProfile is not None: + self.destinationProfile + self.downSample = downSample + if downSampleSize is not None: + self.downSampleSize = downSampleSize + if downSampleSizeLimit is not None: + self.downSampleSizeLimit = downSampleSizeLimit + self.embedColorProfile = embedColorProfile + self.embedThumbnail = embedThumbnail + self.encoding = encoding + self.jpegQuality = jpegQuality + self.layers = layers + self.optimizeForWeb = optimizeForWeb + if outputCondition is not None: + self.outputCondition = outputCondition + if outputConditionID is not None: + self.outputConditionID = outputConditionID + if PDFCompatibility is not None: + self.PDFCompatibility = PDFCompatibility + if PDFStandard is not None: + self.PDFStandard = PDFStandard + self.preserveEditing = preserveEditing + if presetFile is not None: + self.presetFile = presetFile + if profileInclusionPolicy is not None: + self.profileInclusionPolicy = profileInclusionPolicy + if registryName is not None: + self.registryName = registryName + if spotColors is not None: + self.spotColors = spotColors + if tileSize is not None: + self.tileSize = tileSize + self.view = view @property - def alphaChannels(self): + def alphaChannels(self) -> bool: """True to save the alpha channels with the file.""" return self.app.alphaChannels @alphaChannels.setter - def alphaChannels(self, value): + def alphaChannels(self, value: bool) -> None: """True to save the alpha channels with the file.""" self.app.alphaChannels = value @property - def annotations(self): + def annotations(self) -> bool: """If true, the annotations are saved.""" return self.app.anotations @annotations.setter - def annotations(self, value): + def annotations(self, value: bool) -> None: """If true, the annotations are saved.""" self.app.annotations = value @property - def colorConversion(self): + def colorConversion(self) -> bool: """If true, converts the color profile to the destination profile.""" return self.app.colorConversion @colorConversion.setter - def colorConversion(self, value): + def colorConversion(self, value: bool) -> None: """If true, converts the color profile to the destination profile.""" self.app.colorConversion = value @property - def convertToEightBit(self): + def convertToEightBit(self) -> bool: """If true, converts a 16-bit image to 8-bit for better compatibility with other applications.""" return self.app.convertToEightBit @convertToEightBit.setter - def convertToEightBit(self, value): + def convertToEightBit(self, value: bool) -> None: """If true, converts a 16-bit image to 8-bit for better compatibility with other applications.""" self.app.convertToEightBit = value @property - def description(self): + def description(self) -> str: """Description of the save options in use.""" return self.app.description @description.setter - def description(self, text): + def description(self, text: str) -> None: """Description of the save options in use.""" self.app.description = text @property - def destinationProfile(self): + def destinationProfile(self) -> str: """Describes the final RGB or CMYK output device, such as a monitor or press standard.""" try: return self.app.destinationProfile except COMError: raise ValueError( - "Should set value first. " "This parameter can only be read after the " "value has been set." + "Should set value first. " + "This parameter can only be read after the " + "value has been set." ) @destinationProfile.setter - def destinationProfile(self, value): + def destinationProfile(self, value: str) -> None: """Describes the final RGB or CMYK output device, such as a monitor or press standard.""" self.app.destinationProfile = value @property - def downSample(self): + def downSample(self) -> PDFResampleType: """The downsample method to use.""" - return self.app.downSample + return PDFResampleType(self.app.downSample) @downSample.setter - def downSample(self, value): + def downSample(self, value: PDFResampleType) -> None: """The downsample method to use.""" self.app.downSample = value @property - def downSampleSize(self): + def downSampleSize(self) -> float: """The size (in pixels per inch) to downsample images to if they exceed the value specified for down sample size limit.""" try: return self.app.downSampleSize except COMError: raise ValueError( - "Should set value first. " "This parameter can only be read after the " "value has been set." + "Should set value first. " + "This parameter can only be read after the " + "value has been set." ) @downSampleSize.setter - def downSampleSize(self, value): + def downSampleSize(self, value: float) -> None: """The size (in pixels per inch) to downsample images to if they exceed the value specified for ‘down sample size limit’.""" self.app.downSampleSize = value @property - def downSampleSizeLimit(self): + def downSampleSizeLimit(self) -> float: """Limits downsampling or subsampling to images that exceed this value (in pixels per inch).""" try: return self.app.downSampleSizeLimit except COMError: raise ValueError( - "Should set value first. " "This parameter can only be read after the " "value has been set." + "Should set value first. " + "This parameter can only be read after the " + "value has been set." ) @downSampleSizeLimit.setter - def downSampleSizeLimit(self, value: float): + def downSampleSizeLimit(self, value: float) -> None: """Limits downsampling or subsampling to images that exceed this value (in pixels per inch).""" self.app.downSampleSizeLimit = value @property - def embedColorProfile(self): + def embedColorProfile(self) -> bool: """If true, the color profile is embedded in the document.""" return self.app.embedColorProfile @embedColorProfile.setter - def embedColorProfile(self, value: bool): + def embedColorProfile(self, value: bool) -> None: """If true, the color profile is embedded in the document.""" self.app.embedColorProfile = value @property - def embedThumbnail(self): + def embedThumbnail(self) -> bool: """If true, includes a small preview image in Acrobat.""" return self.app.embedThumbnail @embedThumbnail.setter - def embedThumbnail(self, value: bool): + def embedThumbnail(self, value: bool) -> None: """If true, includes a small preview image in Acrobat.""" self.app.embedThumbnail = value @property - def encoding(self): + def encoding(self) -> PDFEncodingType: """The encoding method to use.""" try: - return self.app.encoding + return PDFEncodingType(self.app.encoding) except COMError: - return self.encoding_types.PDFJPEG + return PDFEncodingType.PDFJPEG @encoding.setter - def encoding(self, value: str): + def encoding(self, value: PDFEncodingType) -> None: """The encoding method to use.""" self.app.encoding = value @property - def jpegQuality(self): + def jpegQuality(self) -> int: """Get the quality of the produced image.""" return self.app.jpegQuality @jpegQuality.setter - def jpegQuality(self, quality: int): + def jpegQuality(self, quality: int) -> None: """Set the quality of the produced image. Valid only for JPEG-encoded PDF documents. Range: 0 to 12. @@ -198,159 +257,192 @@ def jpegQuality(self, quality: int): self.app.jpegQuality = quality @property - def layers(self): + def layers(self) -> bool: """If true, the layers are saved.""" return self.app.layers @layers.setter - def layers(self, value: bool): + def layers(self, value: bool) -> None: """If true, the layers are saved.""" self.app.layers = value @property - def optimizeForWeb(self): + def optimizeForWeb(self) -> bool: """If true, improves performance of PDFs on Web servers.""" return self.app.optimizeForWeb @optimizeForWeb.setter - def optimizeForWeb(self, value: bool): + def optimizeForWeb(self, value: bool) -> None: """If true, improves performance of PDFs on Web servers.""" self.app.optimizeForWeb = value @property - def outputCondition(self): + def outputCondition(self) -> str: """An optional comment field for inserting descriptions of the output condition. The text is stored in the PDF/X file.""" return self.app.outputCondition @outputCondition.setter - def outputCondition(self, value): + def outputCondition(self, value: str) -> None: """An optional comment field for inserting descriptions of the output condition. The text is stored in the PDF/X file.""" self.app.outputCondition = value @property - def outputConditionID(self): + def outputConditionID(self) -> str: """The identifier for the output condition.""" try: return self.app.outputConditionID except COMError: raise ValueError( - "Should set value first. " "This parameter can only be read after the " "value has been set." + "Should set value first. " + "This parameter can only be read after the " + "value has been set." ) @outputConditionID.setter - def outputConditionID(self, value): + def outputConditionID(self, value: str) -> None: """The identifier for the output condition.""" self.app.outputConditionID = value @property - def preserveEditing(self): + def PDFCompatibility(self) -> PDFCompatibilityType: + return PDFCompatibilityType(self.app.PDFCompatibility) + + @PDFCompatibility.setter + def PDFCompatibility(self, value: PDFCompatibilityType) -> None: + self.app.PDFCompatibility = value + + @property + def PDFStandard(self) -> PDFStandardType: + return PDFStandardType(self.app.PDFStandard) + + @PDFStandard.setter + def PDFStandard(self, value: PDFStandardType) -> None: + self.app.PDFStandard = value + + @property + def preserveEditing(self) -> bool: """If true, allows users to reopen the PDF in Photoshop with native Photoshop data intact.""" try: return self.app.preserveEditing except COMError: raise ValueError( - "Should set value first. " "This parameter can only be read after the " "value has been set." + "Should set value first. " + "This parameter can only be read after the " + "value has been set." ) @preserveEditing.setter - def preserveEditing(self, value): + def preserveEditing(self, value: bool) -> None: """If true, allows users to reopen the PDF in Photoshop with native Photoshop data intact.""" self.app.preserveEditing = value @property - def presetFile(self): + def presetFile(self) -> str: """The preset file to use for settings; overrides other settings.""" try: return self.app.presetFile except COMError: raise ValueError( - "Should set value first. " "This parameter can only be read after the " "value has been set." + "Should set value first. " + "This parameter can only be read after the " + "value has been set." ) @presetFile.setter - def presetFile(self, file_name): + def presetFile(self, file_name: str) -> None: """The preset file to use for settings; overrides other settings.""" self.app.presetFile = file_name @property - def profileInclusionPolicy(self): + def profileInclusionPolicy(self) -> bool: """If true, shows which profiles to include.""" try: return self.app.profileInclusionPolicy except COMError: raise ValueError( - "Should set value first. " "This parameter can only be read after the " "value has been set." + "Should set value first. " + "This parameter can only be read after the " + "value has been set." ) @profileInclusionPolicy.setter - def profileInclusionPolicy(self, value): + def profileInclusionPolicy(self, value: bool) -> None: """If true, shows which profiles to include.""" self.app.profileInclusionPolicy = value @property - def registryName(self): + def registryName(self) -> str: """The URL where the output condition is registered.""" try: return self.app.registryName except COMError: raise ValueError( - "Should set value first. " "This parameter can only be read after the " "value has been set." + "Should set value first. " + "This parameter can only be read after the " + "value has been set." ) @registryName.setter - def registryName(self, value): + def registryName(self, value: str) -> None: """The URL where the output condition is registered.""" self.app.registryName = value @property - def spotColors(self): + def spotColors(self) -> bool: """If true, the spot colors are saved.""" try: return self.app.spotColors except COMError: raise ValueError( - "Should set value first. " "This parameter can only be read after the " "value has been set." + "Should set value first. " + "This parameter can only be read after the " + "value has been set." ) @spotColors.setter - def spotColors(self, value): + def spotColors(self, value: bool) -> None: """If true, the spot colors are saved.""" self.app.spotColors = value @property - def tileSize(self): + def tileSize(self) -> int: """The compression option. Valid only when encoding is JPEG2000.""" try: return self.app.tileSize except COMError: raise ValueError( - "Should set value first. " "This parameter can only be read after the " "value has been set." + "Should set value first. " + "This parameter can only be read after the " + "value has been set." ) @tileSize.setter - def tileSize(self, value): + def tileSize(self, value: int) -> None: """The compression option. Valid only when encoding is JPEG2000.""" if self.encoding not in ( - self.encoding_types.PDFJPEG2000HIGH, - self.encoding_types.PDFJPEG2000LOSSLESS, - self.encoding_types.PDFJPEG2000MED, - self.encoding_types.PDFJPEG2000MEDLOW, - self.encoding_types.PDFJPEG2000LOW, - self.encoding_types.PDFJPEG2000MEDHIGH, + PDFEncodingType.PDFJPEG2000HIGH, + PDFEncodingType.PDFJPEG2000LOSSLESS, + PDFEncodingType.PDFJPEG2000MED, + PDFEncodingType.PDFJPEG2000MEDLOW, + PDFEncodingType.PDFJPEG2000LOW, + PDFEncodingType.PDFJPEG2000MEDHIGH, ): - raise ValueError("tileSize only work in JPEG2000. Please " "change PDFSaveOptions.encoding to JPEG2000.") + raise ValueError( + "tileSize only work in JPEG2000. Please " + "change PDFSaveOptions.encoding to JPEG2000." + ) self.app.tileSize = value @property - def view(self): + def view(self) -> bool: """If true, opens the saved PDF in Acrobat.""" return self.app.view @view.setter - def view(self, value): + def view(self, value: bool) -> None: """If true, opens the saved PDF in Acrobat.""" self.app.view = value diff --git a/photoshop/api/save_options/png.py b/photoshop/api/save_options/png.py index acf3481a..3fbdc08d 100644 --- a/photoshop/api/save_options/png.py +++ b/photoshop/api/save_options/png.py @@ -1,8 +1,7 @@ # Import local modules from photoshop.api._core import Photoshop -from photoshop.api.enumerations import ColorReductionType -from photoshop.api.enumerations import DitherType -from photoshop.api.enumerations import SaveDocumentType +from photoshop.api.colors.rgb import RGBColor +from photoshop.api.enumerations import ColorReductionType, DitherType, SaveDocumentType class ExportOptionsSaveForWeb(Photoshop): @@ -10,20 +9,19 @@ class ExportOptionsSaveForWeb(Photoshop): object_name = "ExportOptionsSaveForWeb" - def __init__(self): + def __init__(self) -> None: super().__init__() - self._format = SaveDocumentType.PNGSave # Default to PNG self.PNG8 = False # Sets it to PNG-24 bit @property def format(self) -> SaveDocumentType: """The file format to use. One of the SaveDocumentType constants.""" - return self._format + return SaveDocumentType(self.app.format) @format.setter - def format(self, value: SaveDocumentType): + def format(self, value: SaveDocumentType) -> None: """Set the file format to use.""" - self._format = value + self.app.format = value @property def PNG8(self) -> bool: @@ -31,7 +29,7 @@ def PNG8(self) -> bool: return self.app.PNG8 @PNG8.setter - def PNG8(self, value: bool): + def PNG8(self, value: bool) -> None: self.app.PNG8 = value @property @@ -40,16 +38,16 @@ def blur(self) -> float: return self.app.blur @blur.setter - def blur(self, value: float): + def blur(self, value: float) -> None: self.app.blur = value @property def colorReduction(self) -> ColorReductionType: """The color reduction algorithm.""" - return self.app.colorReduction + return ColorReductionType(self.app.colorReduction) @colorReduction.setter - def colorReduction(self, value: ColorReductionType): + def colorReduction(self, value: ColorReductionType) -> None: self.app.colorReduction = value @property @@ -58,25 +56,65 @@ def colors(self) -> int: return self.app.colors @colors.setter - def colors(self, value: int): + def colors(self, value: int) -> None: self.app.colors = value @property def dither(self) -> DitherType: """The type of dither to use.""" - return self.app.dither + return DitherType(self.app.dither) @dither.setter - def dither(self, value: DitherType): + def dither(self, value: DitherType) -> None: self.app.dither = value + @property + def ditherAmount(self) -> int: + return self.app.ditherAmount + + @ditherAmount.setter + def ditherAmount(self, value: int) -> None: + self.app.ditherAmount = value + + @property + def includeProfile(self) -> bool: + return self.app.includeProfile + + @includeProfile.setter + def includeProfile(self, value: bool) -> None: + self.app.includeProfile = value + + @property + def interlaced(self) -> bool: + return self.app.interlaced + + @interlaced.setter + def interlaced(self, value: bool) -> None: + self.app.interlaced = value + + @property + def lossy(self) -> int: + return self.app.lossy + + @lossy.setter + def lossy(self, value: int) -> None: + self.app.lossy = value + + @property + def matteColor(self) -> RGBColor: + return self.app.matteColor + + @matteColor.setter + def matteColor(self, value: RGBColor) -> None: + self.app.matteColor = value + @property def optimized(self) -> bool: """If true, optimization is enabled.""" return self.app.optimized @optimized.setter - def optimized(self, value: bool): + def optimized(self, value: bool) -> None: self.app.optimized = value @property @@ -85,16 +123,48 @@ def quality(self) -> int: return self.app.quality @quality.setter - def quality(self, value: int): + def quality(self, value: int) -> None: self.app.quality = value + @property + def transparency(self) -> bool: + return self.app.transparency + + @transparency.setter + def transparency(self, value: bool) -> None: + self.app.transparency = value + + @property + def transparencyAmount(self) -> int: + return self.app.transparencyAmount + + @transparencyAmount.setter + def transparencyAmount(self, value: int) -> None: + self.app.transparencyAmount = value + + @property + def transparencyDither(self) -> DitherType: + return DitherType(self.app.transparencyDither) + + @transparencyDither.setter + def transparencyDither(self, value: DitherType) -> None: + self.app.transparencyDither = value + + @property + def webSnap(self) -> int: + return self.app.webSnap + + @webSnap.setter + def webSnap(self, value: int) -> None: + self.app.webSnap = value + class PNGSaveOptions(Photoshop): """Options for saving file as PNG.""" object_name = "PNGSaveOptions" - def __init__(self, interlaced: bool = False, compression: int = 6): + def __init__(self, interlaced: bool = False, compression: int = 6) -> None: super().__init__() self.interlaced = interlaced self.compression = compression @@ -104,7 +174,7 @@ def interlaced(self) -> bool: return self.app.interlaced @interlaced.setter - def interlaced(self, value: bool): + def interlaced(self, value: bool) -> None: self.app.interlaced = value @property @@ -112,5 +182,5 @@ def compression(self) -> int: return self.app.compression @compression.setter - def compression(self, value: int): + def compression(self, value: int) -> None: self.app.compression = value diff --git a/photoshop/api/save_options/psd.py b/photoshop/api/save_options/psd.py index 2cb79233..dd90e2e8 100644 --- a/photoshop/api/save_options/psd.py +++ b/photoshop/api/save_options/psd.py @@ -7,50 +7,50 @@ class PhotoshopSaveOptions(Photoshop): object_name = "PhotoshopSaveOptions" - def __int__(self): + def __int__(self) -> None: super().__init__() @property - def alphaChannels(self): + def alphaChannels(self) -> bool: """If true, the alpha channels are saved.""" return self.app.alphaChannels() @alphaChannels.setter - def alphaChannels(self, value): + def alphaChannels(self, value: bool) -> None: self.app.alphaChannels = value @property - def annotations(self): + def annotations(self) -> bool: """If true, the annotations are saved.""" return self.app.annotations() @annotations.setter - def annotations(self, value): + def annotations(self, value: bool) -> None: self.app.annotations = value @property - def embedColorProfile(self): + def embedColorProfile(self) -> bool: """If true, the color profile is embedded in the document.""" return self.app.embedColorProfile() @embedColorProfile.setter - def embedColorProfile(self, value): + def embedColorProfile(self, value: bool) -> None: self.app.embedColorProfile = value @property - def layers(self): + def layers(self) -> bool: """If true, the layers are saved.""" return self.app.layers() @layers.setter - def layers(self, value): + def layers(self, value: bool) -> None: self.app.layers = value @property - def spotColors(self): + def spotColors(self) -> bool: """If true, spot colors are saved.""" return self.app.spotColors() @spotColors.setter - def spotColors(self, value): + def spotColors(self, value: bool) -> None: self.app.spotColors = value diff --git a/photoshop/api/save_options/tag.py b/photoshop/api/save_options/tag.py index 622170b6..16babd36 100644 --- a/photoshop/api/save_options/tag.py +++ b/photoshop/api/save_options/tag.py @@ -8,30 +8,30 @@ class TargaSaveOptions(Photoshop): object_name = "TargaSaveOptions" - def __int__(self): + def __int__(self) -> None: super().__init__() @property - def alphaChannels(self): + def alphaChannels(self) -> bool: """If true, the alpha channels are saved.""" return self.app.alphaChannels @alphaChannels.setter - def alphaChannels(self, value): + def alphaChannels(self, value: bool) -> None: self.app.alphaChannels = value @property - def resolution(self): - return self.app.resolution + def resolution(self) -> TargaBitsPerPixels: + return TargaBitsPerPixels(self.app.resolution) @resolution.setter - def resolution(self, value: TargaBitsPerPixels = TargaBitsPerPixels.Targa24Bits): + def resolution(self, value: TargaBitsPerPixels) -> None: self.app.resolution = value @property - def rleCompression(self): + def rleCompression(self) -> bool: return self.app.rleCompression @rleCompression.setter - def rleCompression(self, value): + def rleCompression(self, value: bool) -> None: self.app.rleCompression = value diff --git a/photoshop/api/save_options/tif.py b/photoshop/api/save_options/tif.py index 79f5379c..32b29ad8 100644 --- a/photoshop/api/save_options/tif.py +++ b/photoshop/api/save_options/tif.py @@ -1,5 +1,10 @@ # Import local modules from photoshop.api._core import Photoshop +from photoshop.api.enumerations import ( + ByteOrderType, + LayerCompressionType, + TiffEncodingType, +) class TiffSaveOptions(Photoshop): @@ -7,68 +12,68 @@ class TiffSaveOptions(Photoshop): object_name = "TiffSaveOptions" - def __int__(self): + def __int__(self) -> None: super().__init__() @property - def alphaChannels(self): + def alphaChannels(self) -> bool: """If true, the alpha channels are saved.""" return self.app.alphaChannels @alphaChannels.setter - def alphaChannels(self, value): + def alphaChannels(self, value: bool) -> None: self.app.alphaChannels = value @property - def annotations(self): + def annotations(self) -> bool: """If true, the annotations are saved.""" return self.app.annotations @annotations.setter - def annotations(self, value): + def annotations(self, value: bool) -> None: self.app.annotations = value @property - def byteOrder(self): + def byteOrder(self) -> ByteOrderType: """The order in which the bytes will be read. Default: Mac OS when running in Mac OS, and IBM PC when running in Windows. """ - return self.app.byteOrder + return ByteOrderType(self.app.byteOrder) @byteOrder.setter - def byteOrder(self, value): + def byteOrder(self, value: ByteOrderType) -> None: self.app.byteOrder = value @property - def embedColorProfile(self): + def embedColorProfile(self) -> bool: """If true, the color profile is embedded in the document.""" return self.app.embedColorProfile @embedColorProfile.setter - def embedColorProfile(self, value): + def embedColorProfile(self, value: bool) -> None: self.app.embedColorProfile = value @property - def imageCompression(self): + def imageCompression(self) -> TiffEncodingType: """The compression type.""" - return self.app.imageCompression + return TiffEncodingType(self.app.imageCompression) @imageCompression.setter - def imageCompression(self, value): + def imageCompression(self, value: TiffEncodingType) -> None: self.app.imageCompression = value @property - def interleaveChannels(self): + def interleaveChannels(self) -> bool: """If true, the channels in the image are interleaved.""" return self.app.interleaveChannels @interleaveChannels.setter - def interleaveChannels(self, value): + def interleaveChannels(self, value: bool) -> None: self.app.interleaveChannels = value @property - def jpegQuality(self): + def jpegQuality(self) -> int: """The quality of the produced image, which is inversely proportionate to the amount of JPEG compression. Valid only for JPEG compressed TIFF documents. Range: 0 to 12. @@ -76,15 +81,15 @@ def jpegQuality(self): return self.app.jpegQuality @jpegQuality.setter - def jpegQuality(self, value): + def jpegQuality(self, value: int) -> None: self.app.jpegQuality = value @property - def layerCompression(self): - return self.app.layerCompression + def layerCompression(self) -> LayerCompressionType: + return LayerCompressionType(self.app.layerCompression) @layerCompression.setter - def layerCompression(self, value): + def layerCompression(self, value: LayerCompressionType) -> None: """The method of compression to use when saving layers (as opposed to saving composite data). Valid only when `layers` = true. @@ -92,38 +97,38 @@ def layerCompression(self, value): self.app.layerCompression = value @property - def layers(self): + def layers(self) -> bool: """If true, the layers are saved.""" return self.app.layers @layers.setter - def layers(self, value): + def layers(self, value: bool) -> None: self.app.layers = value @property - def saveImagePyramid(self): + def saveImagePyramid(self) -> bool: """If true, preserves multi-resolution information.""" return self.app.saveImagePyramid @saveImagePyramid.setter - def saveImagePyramid(self, value): + def saveImagePyramid(self, value: bool) -> None: self.app.saveImagePyramid = value @property - def spotColors(self): + def spotColors(self) -> bool: """If true, spot colors are saved.""" return self.app.spotColors @spotColors.setter - def spotColors(self, value): + def spotColors(self, value: bool) -> None: self.app.spotColors = value @property - def transparency(self): + def transparency(self) -> bool: return self.app.transparency @transparency.setter - def transparency(self, value): + def transparency(self, value: bool) -> None: """If true, saves the transparency as an additional alpha channel when the file is opened in another application.""" self.app.transparency = value diff --git a/photoshop/api/solid_color.py b/photoshop/api/solid_color.py index c0ed302c..8eca4291 100644 --- a/photoshop/api/solid_color.py +++ b/photoshop/api/solid_color.py @@ -23,7 +23,7 @@ class SolidColor(Photoshop): object_name = "SolidColor" - def __init__(self, parent=None): + def __init__(self, parent: Photoshop | None = None) -> None: super().__init__(parent=parent) self._flag_as_method( "isEqual", @@ -40,19 +40,23 @@ def cmyk(self) -> CMYKColor: return CMYKColor(self.app.cmyk) @cmyk.setter - def cmyk(self, value: CMYKColor): + def cmyk(self, value: CMYKColor) -> None: self.app.cmyk = value @property def gray(self) -> GrayColor: return GrayColor(self.app.gray) + @gray.setter + def gray(self, value: GrayColor) -> None: + self.app.gray = value + @property def hsb(self) -> HSBColor: return HSBColor(self.app.hsb) @hsb.setter - def hsb(self, value: HSBColor): + def hsb(self, value: HSBColor) -> None: self.app.hsb = value @property @@ -60,7 +64,7 @@ def lab(self) -> LabColor: return LabColor(self.app.lab) @lab.setter - def lab(self, value: LabColor): + def lab(self, value: LabColor) -> None: self.app.lab = value @property @@ -69,7 +73,7 @@ def model(self) -> ColorModel: return ColorModel(self.app.model) @model.setter - def model(self, value: ColorModel): + def model(self, value: ColorModel) -> None: """The color model.""" self.app.model = value @@ -84,9 +88,9 @@ def rgb(self) -> RGBColor: return RGBColor(self.app.rgb) @rgb.setter - def rgb(self, value: RGBColor): + def rgb(self, value: RGBColor) -> None: self.app.rgb = value - def isEqual(self, color: RGBColor): + def isEqual(self, color: RGBColor) -> bool: """`SolidColor` object is visually equal to the specified color.""" return self.app.isEqual(color) diff --git a/photoshop/api/sub_path_info.py b/photoshop/api/sub_path_info.py new file mode 100644 index 00000000..1e0b81eb --- /dev/null +++ b/photoshop/api/sub_path_info.py @@ -0,0 +1,48 @@ +from typing import Iterator, Sequence + +from photoshop.api._core import Photoshop +from photoshop.api.enumerations import ShapeOperation +from photoshop.api.path_point_info import PathPointInfo + + +class SubPathInfo(Photoshop): + object_name = "SubPathInfo" + + def __init__( + self, + parent: Photoshop | None = None, + entire_sub_path: Sequence[PathPointInfo] | None = None, + closed: bool = True, + operation: ShapeOperation = ShapeOperation.ShapeAdd, + ): + super().__init__(parent=parent) + + if entire_sub_path: + self.entireSubPath = entire_sub_path + self.closed = closed + self.operation = operation + + @property + def closed(self) -> bool: + return self.app.closed + + @closed.setter + def closed(self, value: bool) -> None: + self.app.closed = value + + @property + def entireSubPath(self) -> Iterator[PathPointInfo]: + for point in self.app.entireSubPath: + yield PathPointInfo(point) + + @entireSubPath.setter + def entireSubPath(self, value: Sequence[PathPointInfo]) -> None: + self.app.entireSubPath = value + + @property + def operation(self) -> ShapeOperation: + return ShapeOperation(self.app.operation) + + @operation.setter + def operation(self, value: ShapeOperation) -> None: + self.app.operation = value diff --git a/photoshop/api/sub_path_item.py b/photoshop/api/sub_path_item.py new file mode 100644 index 00000000..95c5ecd5 --- /dev/null +++ b/photoshop/api/sub_path_item.py @@ -0,0 +1,32 @@ +from typing import TYPE_CHECKING, Iterator + +from photoshop.api._core import Photoshop +from photoshop.api.enumerations import ShapeOperation +from photoshop.api.path_point import PathPoint + +if TYPE_CHECKING: + from photoshop.api.path_item import PathItem + + +class SubPathItem(Photoshop): + def __init__(self, parent: Photoshop | None = None) -> None: + super().__init__(parent=parent) + + @property + def closed(self) -> bool: + return self.app.closed + + @property + def operation(self) -> ShapeOperation: + return ShapeOperation(self.app.operation) + + @property + def parent(self) -> "PathItem": + from photoshop.api.path_item import PathItem + + return PathItem(self.app.parent) + + @property + def pathPoints(self) -> Iterator[PathPoint]: + for point in self.app.pathPoints: + yield PathPoint(point) diff --git a/photoshop/api/text_font.py b/photoshop/api/text_font.py index c5e83f82..1bc81488 100644 --- a/photoshop/api/text_font.py +++ b/photoshop/api/text_font.py @@ -5,7 +5,7 @@ class TextFont(Photoshop): """An installed font.""" - def __init__(self, parent=None): + def __init__(self, parent: Photoshop | None = None) -> None: super().__init__(parent=parent) @property @@ -14,16 +14,16 @@ def family(self) -> str: return self.app.family @property - def name(self): + def name(self) -> str: """The name of the font.""" return self.app.name @property - def postScriptName(self): + def postScriptName(self) -> str: """The PostScript name of the font.""" return self.app.postScriptName @property - def style(self): + def style(self) -> str: """The font style.""" return self.app.style diff --git a/photoshop/api/text_item.py b/photoshop/api/text_item.py index 38b338d3..5281099f 100644 --- a/photoshop/api/text_item.py +++ b/photoshop/api/text_item.py @@ -1,22 +1,32 @@ # Import local modules +from typing import TYPE_CHECKING + from photoshop.api._core import Photoshop -from photoshop.api.enumerations import AntiAlias -from photoshop.api.enumerations import AutoKernType -from photoshop.api.enumerations import Direction -from photoshop.api.enumerations import Justification -from photoshop.api.enumerations import Language -from photoshop.api.enumerations import StrikeThruType -from photoshop.api.enumerations import TextComposer -from photoshop.api.enumerations import TextType +from photoshop.api.enumerations import ( + AntiAlias, + AutoKernType, + Case, + Direction, + Justification, + Language, + StrikeThruType, + TextComposer, + TextType, + UnderlineType, + WarpStyle, +) from photoshop.api.solid_color import SolidColor +if TYPE_CHECKING: + from photoshop.api._artlayer import ArtLayer + class TextItem(Photoshop): """The text that is associated with the layer. Valid only when ‘kind’ is text layer.""" object_name = "Application" - def __init__(self, parent): + def __init__(self, parent: Photoshop) -> None: super().__init__(parent=parent) self._flag_as_method( "convertToShape", @@ -24,11 +34,11 @@ def __init__(self, parent): ) @property - def alternateLigatures(self): + def alternateLigatures(self) -> bool: return self.app.alternateLigatures @alternateLigatures.setter - def alternateLigatures(self, value): + def alternateLigatures(self, value: bool) -> None: self.app.alternateLigatures = value @property @@ -37,7 +47,7 @@ def antiAliasMethod(self) -> AntiAlias: return AntiAlias(self.app.antiAliasMethod) @antiAliasMethod.setter - def antiAliasMethod(self, value): + def antiAliasMethod(self, value: AntiAlias) -> None: self.app.antiAliasMethod = value @property @@ -46,15 +56,15 @@ def autoKerning(self) -> AutoKernType: return AutoKernType(self.app.autoKerning) @autoKerning.setter - def autoKerning(self, value): + def autoKerning(self, value: AutoKernType) -> None: self.app.autoKerning = value @property - def autoLeadingAmount(self): + def autoLeadingAmount(self) -> float: return self.app.autoLeadingAmount @autoLeadingAmount.setter - def autoLeadingAmount(self, value): + def autoLeadingAmount(self, value: float) -> None: """The percentage to use for auto (default) leading (in points). Valid only when useAutoLeading = True. @@ -64,21 +74,21 @@ def autoLeadingAmount(self, value): self.app.autoLeadingAmount = value @property - def baselineShift(self): + def baselineShift(self) -> float: """The unit value to use in the baseline offset of text.""" return self.app.baselineShift @baselineShift.setter - def baselineShift(self, value): + def baselineShift(self, value: float) -> None: self.app.baselineShift = value @property - def capitalization(self): + def capitalization(self) -> Case: """Gets text case.""" - return self.app.capitalization + return Case(self.app.capitalization) @capitalization.setter - def capitalization(self, value): + def capitalization(self, value: Case) -> None: """Sets text case.""" self.app.capitalization = value @@ -88,7 +98,7 @@ def color(self) -> SolidColor: return SolidColor(self.app.color) @color.setter - def color(self, color_value): + def color(self, color_value: SolidColor) -> None: """The color of textItem.""" self.app.color = color_value @@ -98,7 +108,7 @@ def contents(self) -> str: return self.app.contents @contents.setter - def contents(self, text: str): + def contents(self, text: str) -> None: """Set the actual text in the layer. Args: @@ -108,18 +118,18 @@ def contents(self, text: str): self.app.contents = text @property - def desiredGlyphScaling(self): + def desiredGlyphScaling(self) -> float: """The desired amount by which to scale the horizontal size of the text letters. A percentage value; at 100, the width of characters is not scaled.""" return self.app.desiredGlyphScaling @desiredGlyphScaling.setter - def desiredGlyphScaling(self, value): + def desiredGlyphScaling(self, value: float) -> None: self.app.desiredGlyphScaling = value @property - def desiredLetterScaling(self): + def desiredLetterScaling(self) -> float: """The amount of space between letters . (at 0, no space is added between letters). Equivalent to Letter Spacing in the Justification @@ -135,11 +145,11 @@ def desiredLetterScaling(self): return self.app.desiredLetterScaling @desiredLetterScaling.setter - def desiredLetterScaling(self, value): + def desiredLetterScaling(self, value: float) -> None: self.app.desiredGlyphScaling = value @property - def desiredWordScaling(self): + def desiredWordScaling(self) -> float: """ The amount (percentage) of space between words (at 100, no additional space is added @@ -158,20 +168,20 @@ def desiredWordScaling(self): return self.app.desiredWordScaling @desiredWordScaling.setter - def desiredWordScaling(self, value): + def desiredWordScaling(self, value: float) -> None: self.app.desiredWordScaling = value @property - def direction(self): + def direction(self) -> Direction: """The text orientation.""" return Direction(self.app.direction) @direction.setter - def direction(self, value): + def direction(self, value: Direction) -> None: self.app.direction = value @property - def fauxBold(self): + def fauxBold(self) -> bool: """True to use faux bold (default: false). Setting this to true is equivalent to selecting text and @@ -181,11 +191,11 @@ def fauxBold(self): return self.app.fauxBold @fauxBold.setter - def fauxBold(self, value): + def fauxBold(self, value: bool) -> None: self.app.fauxBold = value @property - def fauxItalic(self): + def fauxItalic(self) -> bool: """True to use faux italic (default: false). Setting this to true is equivalent to selecting text and @@ -195,16 +205,16 @@ def fauxItalic(self): return self.app.fauxItalic @fauxItalic.setter - def fauxItalic(self, value): + def fauxItalic(self, value: bool) -> None: self.app.fauxItalic = value @property - def firstLineIndent(self): + def firstLineIndent(self) -> float: """The amount (unit value) to indent the first line of paragraphs.""" return self.app.firstLineIndent @firstLineIndent.setter - def firstLineIndent(self, value): + def firstLineIndent(self, value: float) -> None: self.app.firstLineIndent = value @property @@ -213,7 +223,7 @@ def font(self) -> str: return self.app.font @font.setter - def font(self, text_font: str): + def font(self, text_font: str) -> None: """Set the font of this TextItem. Args: @@ -227,16 +237,16 @@ def hangingPunctuation(self) -> bool: return self.app.hangingPunctuation @hangingPunctuation.setter - def hangingPunctuation(self, value: bool): + def hangingPunctuation(self, value: bool) -> None: self.app.hangingPunctuation = value @property - def height(self): + def height(self) -> float: """int: The height of the bounding box for paragraph text.""" return self.app.height @height.setter - def height(self, value): + def height(self, value: float) -> None: self.app.height = value @property @@ -245,7 +255,7 @@ def horizontalScale(self) -> int: return self.app.horizontalScale @horizontalScale.setter - def horizontalScale(self, value: int): + def horizontalScale(self, value: int) -> None: """Set the horizontalScale of this TextItem. Args: @@ -254,122 +264,122 @@ def horizontalScale(self, value: int): self.app.horizontalScale = value @property - def hyphenateAfterFirst(self): + def hyphenateAfterFirst(self) -> int: """The number of letters after which hyphenation in word wrap is allowed.""" return self.app.hyphenateAfterFirst @hyphenateAfterFirst.setter - def hyphenateAfterFirst(self, value): + def hyphenateAfterFirst(self, value: int) -> None: self.app.hyphenateAfterFirst = value @property - def hyphenateBeforeLast(self): + def hyphenateBeforeLast(self) -> int: """The number of letters before which hyphenation in word wrap is allowed.""" return self.app.hyphenateBeforeLast @hyphenateBeforeLast.setter - def hyphenateBeforeLast(self, value): + def hyphenateBeforeLast(self, value: int) -> None: self.app.hyphenateBeforeLast = value @property - def hyphenateCapitalWords(self): + def hyphenateCapitalWords(self) -> bool: """True to allow hyphenation in word wrap of capitalized words""" return self.app.hyphenateCapitalWords @hyphenateCapitalWords.setter - def hyphenateCapitalWords(self, value): + def hyphenateCapitalWords(self, value: bool) -> None: self.app.hyphenateCapitalWords = value @property - def hyphenateWordsLongerThan(self): + def hyphenateWordsLongerThan(self) -> int: """The minimum number of letters a word must have in order for hyphenation in word wrap to be allowed.""" return self.app.hyphenateWordsLongerThan @hyphenateWordsLongerThan.setter - def hyphenateWordsLongerThan(self, value): + def hyphenateWordsLongerThan(self, value: int) -> None: self.app.hyphenateWordsLongerThan = value @property - def hyphenation(self): + def hyphenation(self) -> bool: """True to use hyphenation in word wrap.""" return self.app.hyphenation @hyphenation.setter - def hyphenation(self, value): + def hyphenation(self, value: bool) -> None: self.app.hyphenation = value @property - def hyphenationZone(self): + def hyphenationZone(self) -> float: """The distance at the end of a line that will cause a word to break in unjustified type.""" return self.app.hyphenationZone @hyphenationZone.setter - def hyphenationZone(self, value): + def hyphenationZone(self, value: float) -> None: self.app.hyphenationZone = value @property - def hyphenLimit(self): + def hyphenLimit(self) -> int: return self.app.hyphenLimit @hyphenLimit.setter - def hyphenLimit(self, value): + def hyphenLimit(self, value: int) -> None: self.app.hyphenLimit = value @property - def justification(self): + def justification(self) -> Justification: """The paragraph justification.""" return Justification(self.app.justification) @justification.setter - def justification(self, value): + def justification(self, value: Justification) -> None: self.app.justification = value @property - def kind(self): + def kind(self) -> TextType: return TextType(self.app.kind) @kind.setter - def kind(self, kind_type): + def kind(self, kind_type: TextType) -> None: self.app.kind = kind_type @property - def language(self): + def language(self) -> Language: return Language(self.app.language) @language.setter - def language(self, text): + def language(self, text: Language) -> None: self.app.language = text @property - def leading(self): + def leading(self) -> float: return self.app.leading @leading.setter - def leading(self, value): + def leading(self, value: float) -> None: self.app.leading = value @property - def leftIndent(self): + def leftIndent(self) -> float: """The amoun of space to indent text from the left.""" return self.app.leftIndent @leftIndent.setter - def leftIndent(self, value): + def leftIndent(self, value: float) -> None: self.app.leftIndent = value @property - def ligatures(self): + def ligatures(self) -> bool: """True to use ligatures.""" return self.app.ligatures @ligatures.setter - def ligatures(self, value): + def ligatures(self, value: bool) -> None: self.app.ligatures = value @property - def maximumGlyphScaling(self): + def maximumGlyphScaling(self) -> float: """The maximum amount to scale the horizontal size of the text letters (a percentage value; at 100, the width of characters is not scaled). @@ -384,11 +394,11 @@ def maximumGlyphScaling(self): return self.app.maximumGlyphScaling @maximumGlyphScaling.setter - def maximumGlyphScaling(self, value): + def maximumGlyphScaling(self, value: float) -> None: self.app.maximumGlyphScaling = value @property - def maximumLetterScaling(self): + def maximumLetterScaling(self) -> float: """The maximum amount of space to allow between letters (at 0, no space is added between letters). @@ -406,19 +416,19 @@ def maximumLetterScaling(self): return self.app.maximumLetterScaling @maximumLetterScaling.setter - def maximumLetterScaling(self, value): + def maximumLetterScaling(self, value: float) -> None: self.app.maximumLetterScaling = value @property - def maximumWordScaling(self): + def maximumWordScaling(self) -> float: return self.app.maximumWordScaling @maximumWordScaling.setter - def maximumWordScaling(self, value): + def maximumWordScaling(self, value: float) -> None: self.app.maximumWordScaling = value @property - def minimumGlyphScaling(self): + def minimumGlyphScaling(self) -> float: """The minimum amount to scale the horizontal size of the text letters (a percentage value; at 100, the width of characters is not scaled). @@ -433,11 +443,11 @@ def minimumGlyphScaling(self): return self.app.minimumGlyphScaling @minimumGlyphScaling.setter - def minimumGlyphScaling(self, value): + def minimumGlyphScaling(self, value: float) -> None: self.app.minimumGlyphScaling = value @property - def minimumLetterScaling(self): + def minimumLetterScaling(self) -> float: """The minimum amount of space to allow between letters (a percentage value; at 0, no space is removed between letters). @@ -456,11 +466,11 @@ def minimumLetterScaling(self): return self.app.minimumLetterScaling @minimumLetterScaling.setter - def minimumLetterScaling(self, value): + def minimumLetterScaling(self, value: float) -> None: self.app.minimumLetterScaling = value @property - def minimumWordScaling(self): + def minimumWordScaling(self) -> float: """The minimum amount of space to allow between words (a percentage value; at 100, no additional space is removed between words). @@ -479,11 +489,11 @@ def minimumWordScaling(self): return self.app.minimumWordScaling @minimumWordScaling.setter - def minimumWordScaling(self, value): + def minimumWordScaling(self, value: float) -> None: self.app.minimumWordScaling = value @property - def noBreak(self): + def noBreak(self) -> bool: """True to disallow line breaks in this text. Tip: When true for many consecutive characters, can @@ -494,31 +504,33 @@ def noBreak(self): return self.app.noBreak @noBreak.setter - def noBreak(self, value): + def noBreak(self, value: bool) -> None: self.app.noBreak = value @property - def oldStyle(self): + def oldStyle(self) -> bool: return self.app.oldStyle @oldStyle.setter - def oldStyle(self, value): + def oldStyle(self, value: bool) -> None: self.app.oldStyle = value @property - def parent(self): - return self.app.parent + def parent(self) -> "ArtLayer": + from ._artlayer import ArtLayer + + return ArtLayer(self.app.parent) @parent.setter - def parent(self, value): + def parent(self, value: "ArtLayer") -> None: self.app.parent = value @property - def position(self): + def position(self) -> tuple[int, int]: return self.app.position @position.setter - def position(self, array): + def position(self, array: tuple[int, int]) -> None: """The position of the origin for the text. The array must contain two values. Setting this property is basically @@ -529,15 +541,15 @@ def position(self, array): self.app.position = array @property - def rightIndent(self): + def rightIndent(self) -> float: return self.app.rightIndent @rightIndent.setter - def rightIndent(self, value): + def rightIndent(self, value: float) -> None: self.app.rightIndent = value @property - def size(self): + def size(self) -> float: """The font size in UnitValue. NOTE: Type was points for CS3 and older. @@ -546,83 +558,83 @@ def size(self): return self.app.size @size.setter - def size(self, value): + def size(self, value: float) -> None: self.app.size = value @property - def spaceAfter(self): + def spaceAfter(self) -> float: """The amount of space to use after each paragraph.""" return self.app.spaceAfter @spaceAfter.setter - def spaceAfter(self, value): + def spaceAfter(self, value: float) -> None: self.app.spaceAfter = value @property - def spaceBefore(self): + def spaceBefore(self) -> float: return self.app.spaceBefore @spaceBefore.setter - def spaceBefore(self, value): + def spaceBefore(self, value: float) -> None: self.app.spaceBefore = value @property - def strikeThru(self): + def strikeThru(self) -> StrikeThruType: """The text strike-through option to use.""" return StrikeThruType(self.app.strikeThru) @strikeThru.setter - def strikeThru(self, value): + def strikeThru(self, value: StrikeThruType) -> None: self.app.strikeThru = value @property - def textComposer(self): + def textComposer(self) -> TextComposer: return TextComposer(self.app.textComposer) @textComposer.setter - def textComposer(self, value): + def textComposer(self, value: TextComposer) -> None: self.app.textComposer = value @property - def tracking(self): + def tracking(self) -> float: return self.app.tracking @tracking.setter - def tracking(self, value): + def tracking(self, value: float) -> None: self.app.tracking = value @property - def underline(self): + def underline(self) -> UnderlineType: """The text underlining options.""" - return self.app.underline + return UnderlineType(self.app.underline) @underline.setter - def underline(self, value): + def underline(self, value: UnderlineType) -> None: self.app.underline = value @property - def useAutoLeading(self): + def useAutoLeading(self) -> bool: return self.app.useAutoLeading @useAutoLeading.setter - def useAutoLeading(self, value): + def useAutoLeading(self, value: bool) -> None: self.app.useAutoLeading = value @property - def verticalScale(self): + def verticalScale(self) -> int: return self.app.verticalScale @verticalScale.setter - def verticalScale(self, value): + def verticalScale(self, value: int) -> None: self.app.verticalScale = value @property - def warpBend(self): + def warpBend(self) -> float: """The warp bend percentage.""" return self.app.warpBend @warpBend.setter - def warpBend(self, value): + def warpBend(self, value: float) -> None: self.app.warpBend = value @property @@ -631,36 +643,36 @@ def warpDirection(self) -> Direction: return Direction(self.app.warpDirection) @warpDirection.setter - def warpDirection(self, value): + def warpDirection(self, value: Direction) -> None: self.app.warpDirection = value @property - def warpHorizontalDistortion(self): + def warpHorizontalDistortion(self) -> float: return self.app.warpHorizontalDistortion @warpHorizontalDistortion.setter - def warpHorizontalDistortion(self, value): + def warpHorizontalDistortion(self, value: float) -> None: self.app.warpHorizontalDistortion = value @property - def warpStyle(self): + def warpStyle(self) -> WarpStyle: """The style of warp to use.""" - return self.app.warpStyle + return WarpStyle(self.app.warpStyle) @warpStyle.setter - def warpStyle(self, value): + def warpStyle(self, value: WarpStyle) -> None: self.app.warpStyle = value @property - def warpVerticalDistortion(self): + def warpVerticalDistortion(self) -> float: return self.app.warpVerticalDistortion @warpVerticalDistortion.setter - def warpVerticalDistortion(self, value): + def warpVerticalDistortion(self, value: float) -> None: self.app.warpVerticalDistortion = value @property - def width(self): + def width(self) -> float: """The width of the bounding box for paragraph text. @@ -670,7 +682,7 @@ def width(self): return self.app.width @width.setter - def width(self, value: float): + def width(self, value: float) -> None: """The width of the bounding box for paragraph text. @@ -679,15 +691,15 @@ def width(self, value: float): """ self.app.width = value - def convertToShape(self): + def convertToShape(self) -> None: """Converts the text item and its containing layer to a fill layer with the text changed to a clipping path.""" - return self.app.convertToShape() + self.app.convertToShape() - def createPath(self): + def createPath(self) -> None: """Creates a clipping path from the outlines of the actual text items (such as letters or words). """ - return self.app.createPath() + self.app.createPath() diff --git a/photoshop/session.py b/photoshop/session.py index d5976e1b..41cb67e7 100644 --- a/photoshop/session.py +++ b/photoshop/session.py @@ -27,38 +27,44 @@ """ # Import built-in modules -from typing import Any +from contextlib import AbstractContextManager +from os import PathLike +from types import TracebackType +from typing import Any, Literal # Import local modules -from photoshop.api import ActionDescriptor -from photoshop.api import ActionList -from photoshop.api import ActionReference -from photoshop.api import Application -from photoshop.api import BMPSaveOptions -from photoshop.api import BatchOptions -from photoshop.api import CMYKColor -from photoshop.api import EPSSaveOptions -from photoshop.api import EventID -from photoshop.api import ExportOptionsSaveForWeb -from photoshop.api import GIFSaveOptions -from photoshop.api import GrayColor -from photoshop.api import HSBColor -from photoshop.api import JPEGSaveOptions -from photoshop.api import LabColor -from photoshop.api import PDFSaveOptions -from photoshop.api import PNGSaveOptions -from photoshop.api import PhotoshopSaveOptions -from photoshop.api import RGBColor -from photoshop.api import SolidColor -from photoshop.api import TargaSaveOptions -from photoshop.api import TextItem -from photoshop.api import TiffSaveOptions -from photoshop.api import enumerations -from photoshop.api import errors +from photoshop.api import ( + ActionDescriptor, + ActionList, + ActionReference, + Application, + BatchOptions, + BMPSaveOptions, + CMYKColor, + EPSSaveOptions, + EventID, + ExportOptionsSaveForWeb, + GIFSaveOptions, + GrayColor, + HSBColor, + JPEGSaveOptions, + LabColor, + PDFSaveOptions, + PhotoshopSaveOptions, + PNGSaveOptions, + RGBColor, + SolidColor, + TargaSaveOptions, + TextItem, + TiffSaveOptions, + enumerations, + errors, +) +from photoshop.api._document import Document # pylint: disable=too-many-arguments -class Session: +class Session(AbstractContextManager["Session"]): """Session of photoshop. We can control active documents in this Session. @@ -72,11 +78,11 @@ class Session: def __init__( self, - file_path: str = None, - action: str = None, + file_path: str | PathLike[str] | None = None, + action: Literal["open", "new_document", "document_duplicate"] | None = None, callback: Any = None, auto_close: bool = False, - ps_version: str = None, + ps_version: str | None = None, ): """Session of Photoshop. @@ -87,7 +93,7 @@ def __init__( from photoshop import Session with Session("your/psd/or/psb/file_path.psd", action="open") as ps: - ps.echo(ps.active_document.name) + print(ps.active_document.name) ``` Args: @@ -116,11 +122,11 @@ def __init__( """ super().__init__() - self.path = file_path + self.path: str | PathLike[str] | None = file_path self._auto_close = auto_close self._callback = callback self._action = action - self._active_document = None + self._active_document: Document | None = None self.app: Application = Application(version=ps_version) self.ActionReference: ActionReference = ActionReference() @@ -198,7 +204,9 @@ def __init__( self.GalleryConstrainType = enumerations.GalleryConstrainType self.GalleryFontType = enumerations.GalleryFontType self.GallerySecurityTextColorType = enumerations.GallerySecurityTextColorType - self.GallerySecurityTextPositionType = enumerations.GallerySecurityTextPositionType + self.GallerySecurityTextPositionType = ( + enumerations.GallerySecurityTextPositionType + ) self.GallerySecurityTextRotateType = enumerations.GallerySecurityTextRotateType self.GallerySecurityType = enumerations.GallerySecurityType self.GalleryThumbSizeType = enumerations.GalleryThumbSizeType @@ -277,17 +285,17 @@ def __init__( self.UnderlineType = enumerations.UnderlineType self.Units = enumerations.Units self.Urgency = enumerations.Urgency - self.Wartyle = enumerations.Wartyle + self.WarpStyle = enumerations.WarpStyle self.WaveType = enumerations.WaveType self.WhiteBalanceType = enumerations.WhiteBalanceType self.ZigZagType = enumerations.ZigZagType @property - def active_document(self): + def active_document(self) -> Document: """Get current active document. Raises: - - PhotoshopPythonAPICOMError: No active document available. + - PhotoshopPythonAPIError: No active document available. """ try: @@ -297,12 +305,7 @@ def active_document(self): except errors.PhotoshopPythonAPICOMError: raise errors.PhotoshopPythonAPIError("No active document available.") - @staticmethod - def echo(*args, **kwargs): - """Print message.""" - print(*args, **kwargs) - - def alert(self, text: str): + def alert(self, text: str) -> None: """Alert message box in photoshop. Args: @@ -312,36 +315,44 @@ def alert(self, text: str): self.app.doJavaScript(f"alert('{text}')") @active_document.setter - def active_document(self, active_document): + def active_document(self, active_document: Document) -> None: """Set active document.""" self._active_document = active_document - def _action_open(self): - self.active_document = self.app.open(self.path) + def _action_open(self) -> None: + if self.path: + self.active_document = self.app.open(self.path) + else: + print("Specify the document's path before trying to open it.") - def _action_new_document(self): + def _action_new_document(self) -> None: self.active_document = self.app.documents.add() - def _action_document_duplicate(self): + def _action_document_duplicate(self) -> None: self.active_document = self.active_document.duplicate() - def run_action(self): + def run_action(self) -> None: try: _action = getattr(self, f"_action_{self._action}") _action() except AttributeError: pass - def close(self): + def close(self) -> None: """closing current session.""" if self._auto_close: self.active_document.close() - def __enter__(self): + def __enter__(self) -> "Session": self.run_action() return self - def __exit__(self, exc_type, exc_val, exc_tb): + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: try: if self._callback: self._callback(self) diff --git a/test/manual_test/manual_test_application.py b/test/manual_test/manual_test_application.py index 26296062..b77426ab 100644 --- a/test/manual_test/manual_test_application.py +++ b/test/manual_test/manual_test_application.py @@ -1,4 +1,5 @@ """""" + # Import third-party modules import pytest @@ -6,6 +7,7 @@ from photoshop.api import Application from photoshop.api import EventID from photoshop.api import SolidColor +from photoshop.api.enumerations import FontSize class TestApplication: @@ -45,7 +47,10 @@ def test_get_background_color(self): def test_set_background_color(self, photoshop_app): self.app.backgroundColor.rgb.green = 0 - assert self.app.backgroundColor.rgb.green == photoshop_app.backgroundColor.rgb.green + assert ( + self.app.backgroundColor.rgb.green + == photoshop_app.backgroundColor.rgb.green + ) def test_build(self): assert self.app.build == "21.0 (20191018.r.37 2019/10/18: 614690fb487)" @@ -151,6 +156,7 @@ def test_get_version(self): assert self.app.version == "21.1.2" def test_windowsFileTypes(self): + assert isinstance(self.app.windowsFileTypes, tuple) assert len(self.app.windowsFileTypes) >= 100 def test_batch(self): @@ -168,9 +174,6 @@ def test_changeProgressText(self): def test_charIDToTypeID(self): assert self.app.charIDToTypeID("Type") == "1417244773" - def test_compareWithNumbers(self): - assert self.app.compareWithNumbers(20, 1) - def test_do_action(self): self.app.doAction("Vignette (selection)", "Default Actions") @@ -219,3 +222,16 @@ def test_typeIDToStringID(self): def test_updateProgress(self): assert self.app.updateProgress("Done", "total") + + def test_ui_text_font_size(self): + prefs = self.app.preferences + initial_font_size = prefs.textFontSize + try: + prefs.textFontSize = FontSize.Large + assert prefs.textFontSize == FontSize.Large + prefs.textFontSize = FontSize.Medium + assert prefs.textFontSize == FontSize.Medium + prefs.textFontSize = FontSize.Small + assert prefs.textFontSize == FontSize.Small + finally: + prefs.textFontSize = initial_font_size diff --git a/test/manual_test/manual_test_new_document.py b/test/manual_test/manual_test_new_document.py new file mode 100644 index 00000000..f4b1b27a --- /dev/null +++ b/test/manual_test/manual_test_new_document.py @@ -0,0 +1,78 @@ +from pathlib import Path +from tempfile import TemporaryDirectory + +import pytest + +from photoshop import Session +from photoshop.api._artlayer import ArtLayer +from photoshop.api._layerSet import LayerSet +from photoshop.api.enumerations import LayerKind + + +class TestNewDocument: + """Test various parts of the API in a new document.""" + + @pytest.fixture(autouse=True) + def setup(self): + """Setup for current test.""" + self.session = Session(action="new_document", auto_close=True) + self.session.run_action() + self.app = self.session.app + self.doc = self.session.active_document + yield + self.session.close() + + def test_channel_histogram(self): + channel = self.doc.activeChannels[0] + assert isinstance(channel.histogram, tuple) + + def test_document_info_keywords(self): + doc_info = self.doc.info + assert doc_info.keywords is None + doc_info.keywords = ["foo", "bar"] + assert isinstance(doc_info.keywords, tuple) + assert doc_info.keywords == ("foo", "bar") + tuple_keywords = ("key", "word", "?") + doc_info.keywords = tuple_keywords + assert doc_info.keywords == tuple_keywords + + def test_selection(self): + selection = self.doc.selection + selection.selectAll() + bounds = selection.bounds + assert isinstance(bounds, tuple) + assert isinstance(bounds[0], float) + selection.contract(20.5) + contracted_bounds = selection.bounds + assert bounds[0] < contracted_bounds[0] + selection.expand(10) + expanded_bounds = selection.bounds + assert expanded_bounds[0] < contracted_bounds[0] + + def test_paths(self): + with TemporaryDirectory(prefix="photoshop_python_api_") as tmpdir: + doc_path = Path(tmpdir, "test_doc.psd") + self.doc.saveAs(str(doc_path)) + assert isinstance(self.doc.saved, bool) + assert self.doc.saved + assert self.doc.fullName == doc_path + + def test_layer_kind(self): + background_layer = self.doc.artLayers[0] + assert isinstance(background_layer, ArtLayer) + layer_kind = background_layer.kind + layer_set_1 = self.doc.layerSets.add() + self.doc.activeLayer = layer_set_1 + + # This used to fail in version 0.24.1 + assert background_layer.kind == layer_kind + + layer_1 = self.doc.artLayers.add() + layer_1.kind = LayerKind.TextLayer + assert layer_1.kind == LayerKind.TextLayer + + def test_layer_iteration(self): + self.doc.layerSets.add() + layers = list(self.doc.layers) + assert any((layer for layer in layers if isinstance(layer, LayerSet))) + assert any((layer for layer in layers if isinstance(layer, ArtLayer))) diff --git a/test/manual_test/manual_test_path_items.py b/test/manual_test/manual_test_path_items.py new file mode 100644 index 00000000..1e812b37 --- /dev/null +++ b/test/manual_test/manual_test_path_items.py @@ -0,0 +1,78 @@ +from typing import Sequence + +import pytest + +from photoshop.api.enumerations import PointKind, ShapeOperation, ToolType +from photoshop.api.path_item import PathItem +from photoshop.api.path_point_info import PathPointInfo +from photoshop.api.solid_color import SolidColor +from photoshop.api.sub_path_info import SubPathInfo +from photoshop.session import Session + + +class TestPathItems: + """Test path items.""" + + @pytest.fixture(autouse=True) + def setup(self): + self.session = Session(action="new_document", auto_close=True) + self.session.run_action() + self.app = self.session.app + self.doc = self.session.active_document + + self.point_anchors = ((0, 0.0), (10.1, 0.0), (5, 5)) + + self.path_item = self._create_path("test_path", self.point_anchors) + + yield + self.session.close() + + def _create_path( + self, name: str, point_anchors: Sequence[tuple[float, float]] + ) -> PathItem: + point_infos: list[PathPointInfo] = [ + PathPointInfo(anchor=anchor) for anchor in point_anchors + ] + + last_anchor = point_anchors[-1] + left = (last_anchor[0] + 1, last_anchor[1]) + right = (last_anchor[0], last_anchor[1] - 1) + + last_point = point_infos[-1] + last_point.kind = PointKind.SmoothPoint + last_point.leftDirection = left + last_point.rightDirection = right + + sub_path = SubPathInfo(entire_sub_path=point_infos) + + return self.doc.pathItems.add(name=name, entire_path=(sub_path,)) + + def test_path_item(self) -> None: + for sub_path in self.path_item.subPathItems: + assert ShapeOperation(sub_path.operation) + assert isinstance(sub_path.closed, bool) + for point in sub_path.pathPoints: + assert isinstance(point.anchor, tuple) + assert len(point.anchor) == 2 + assert PointKind(point.kind) + solid_color = SolidColor() + solid_color.rgb.red = 100 + solid_color.rgb.green = 100 + solid_color.rgb.blue = 100 + self.path_item.fillPath(solid_color) + self.path_item.strokePath(ToolType.Blur) + + def test_path_removal(self) -> None: + n_paths = 5 + for i in range(n_paths): + self._create_path(f"test_{i}", self.point_anchors) + + path_items = self.doc.pathItems + + path_items_len = len(path_items) + assert path_items_len >= n_paths + path_items[0].remove() + path_items["test_3"].remove() + assert len(path_items) == path_items_len - 2 + path_items.removeAll() + assert len(path_items) == 0 diff --git a/test/manual_test/manual_test_text_item.py b/test/manual_test/manual_test_text_item.py index 08f1b120..62fdf8a4 100644 --- a/test/manual_test/manual_test_text_item.py +++ b/test/manual_test/manual_test_text_item.py @@ -1,10 +1,12 @@ """""" + # Import third-party modules import pytest # Import local modules from photoshop import Session -from photoshop.api.enumerations import TextType +from photoshop.api._artlayer import ArtLayer +from photoshop.api.enumerations import Justification, TextType class TestTextItem: @@ -14,11 +16,14 @@ class TestTextItem: @pytest.fixture(autouse=True) def setup(self, psd_file): """Setup for current test.""" - self.session = Session(file_path=psd_file("textitem"), action="open", auto_close=True) + self.session = Session( + file_path=psd_file("textitem"), action="open", auto_close=True + ) self.session.run_action() doc = self.session.active_document layer = doc.activeLayer - self.text_item = layer.textItem() # -> TextItem + assert isinstance(layer, ArtLayer) + self.text_item = layer.textItem # -> TextItem yield # self.session.close() @@ -72,8 +77,8 @@ def test_justification(self): assert self.text_item.justification == 1 def test_set_justification(self): - self.text_item.justification = 2 - assert self.text_item.justification == 2 + self.text_item.justification = Justification.Center + assert self.text_item.justification == Justification.Center def test_kind(self): assert self.text_item.kind == 1 @@ -95,3 +100,6 @@ def test_size(self): def test_change_size(self): self.text_item.size = 20 assert self.text_item.size == 20.0 + + def test_width(self): + assert isinstance(self.text_item.width, float) From a2b308fd0312ace1f14ec7d81592472954e46330 Mon Sep 17 00:00:00 2001 From: pappnu Date: Wed, 13 Aug 2025 09:52:58 +0300 Subject: [PATCH 02/12] fix(Layer): return parent with correct type --- photoshop/api/_layer.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/photoshop/api/_layer.py b/photoshop/api/_layer.py index 3ade37e3..f9ca6fcb 100644 --- a/photoshop/api/_layer.py +++ b/photoshop/api/_layer.py @@ -84,11 +84,18 @@ def opacity(self, value: float) -> None: self.app.opacity = value @property - def parent(self) -> "Document": - """The object's container.""" - from photoshop.api._document import Document - - return Document(self.app.parent) + def parent(self) -> "Document | LayerSet": + """The layers's container.""" + parent = self.app.parent + try: + parent.resolution + from photoshop.api._document import Document + + return Document(parent) + except NameError: + from photoshop.api._layerSet import LayerSet + + return LayerSet(parent) @property def visible(self) -> bool: From 2089887d85539bb3eb665ff739a02774cc2874ca Mon Sep 17 00:00:00 2001 From: pappnu Date: Wed, 13 Aug 2025 09:55:06 +0300 Subject: [PATCH 03/12] fix(LayerSet): fix iterating LayerSet object directly --- photoshop/api/_layerSet.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/photoshop/api/_layerSet.py b/photoshop/api/_layerSet.py index 4b5d4916..35ef02e4 100644 --- a/photoshop/api/_layerSet.py +++ b/photoshop/api/_layerSet.py @@ -70,5 +70,5 @@ def merge(self) -> ArtLayer: return ArtLayer(self.app.merge()) def __iter__(self) -> Iterator[Layer]: - for layer in self.app: - yield Layer(layer) + for layer in self.layers: + yield layer From 6d7429f22620fc8d6f28205d5255baa3f042858f Mon Sep 17 00:00:00 2001 From: pappnu Date: Wed, 13 Aug 2025 09:57:19 +0300 Subject: [PATCH 04/12] test(TestNewDocument): test getting layer parent and expand layer iteration test --- test/manual_test/manual_test_new_document.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/test/manual_test/manual_test_new_document.py b/test/manual_test/manual_test_new_document.py index f4b1b27a..6487b54b 100644 --- a/test/manual_test/manual_test_new_document.py +++ b/test/manual_test/manual_test_new_document.py @@ -5,6 +5,7 @@ from photoshop import Session from photoshop.api._artlayer import ArtLayer +from photoshop.api._document import Document from photoshop.api._layerSet import LayerSet from photoshop.api.enumerations import LayerKind @@ -72,7 +73,18 @@ def test_layer_kind(self): assert layer_1.kind == LayerKind.TextLayer def test_layer_iteration(self): - self.doc.layerSets.add() + layer_set = self.doc.layerSets.add() layers = list(self.doc.layers) assert any((layer for layer in layers if isinstance(layer, LayerSet))) assert any((layer for layer in layers if isinstance(layer, ArtLayer))) + + layer_set.artLayers.add() + layer_set.layerSets.add() + assert any((layer for layer in layer_set if isinstance(layer, LayerSet))) + assert any((layer for layer in layer_set if isinstance(layer, ArtLayer))) + + def test_layer_parent(self): + layer_set = self.doc.layerSets.add() + assert isinstance(layer_set.parent, Document) + sub_layer = layer_set.artLayers.add() + assert isinstance(sub_layer.parent, LayerSet) From 121328a2983053a9e03ecb3c0427c1d2064e9f28 Mon Sep 17 00:00:00 2001 From: pappnu Date: Wed, 13 Aug 2025 12:28:24 +0300 Subject: [PATCH 05/12] feat: add py.typed file to indicate that the package contains type annotations --- photoshop/py.typed | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 photoshop/py.typed diff --git a/photoshop/py.typed b/photoshop/py.typed new file mode 100644 index 00000000..e69de29b From 94d4344d8d33a808bb4f12f23233eaf7bff5fd4c Mon Sep 17 00:00:00 2001 From: pappnu Date: Wed, 13 Aug 2025 12:59:34 +0300 Subject: [PATCH 06/12] fix(executeActionGet): correct input argument type --- photoshop/api/application.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/photoshop/api/application.py b/photoshop/api/application.py index 96970e2d..9281a302 100644 --- a/photoshop/api/application.py +++ b/photoshop/api/application.py @@ -31,6 +31,7 @@ from photoshop.api._preferences import Preferences from photoshop.api._text_fonts import TextFonts from photoshop.api.action_descriptor import ActionDescriptor +from photoshop.api.action_reference import ActionReference from photoshop.api.batch_options import BatchOptions from photoshop.api.enumerations import DialogModes, PurgeTarget from photoshop.api.errors import PhotoshopPythonAPIError @@ -396,7 +397,7 @@ def executeAction( self.app.executeAction(event_id, descriptor, display_dialogs) ) - def executeActionGet(self, reference: ActionDescriptor) -> ActionDescriptor: + def executeActionGet(self, reference: ActionReference) -> ActionDescriptor: return ActionDescriptor(self.app.executeActionGet(reference)) def featureEnabled(self, name: str) -> bool: From a8dedc49d90f7bd1d118e8edaeaebf941eaf917a Mon Sep 17 00:00:00 2001 From: pappnu Date: Thu, 14 Aug 2025 18:43:55 +0300 Subject: [PATCH 07/12] fix(Application): executeAction can be called without an ActionDescriptor --- photoshop/api/application.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/photoshop/api/application.py b/photoshop/api/application.py index 9281a302..9e8286cb 100644 --- a/photoshop/api/application.py +++ b/photoshop/api/application.py @@ -390,7 +390,7 @@ def eraseCustomOptions(self, key: str) -> None: def executeAction( self, event_id: int, - descriptor: ActionDescriptor, + descriptor: ActionDescriptor | None = None, display_dialogs: DialogModes = DialogModes.DisplayNoDialogs, ) -> ActionDescriptor: return ActionDescriptor( From 0b975b9aa449a2ceb5b917a2ebd60a25e8b5f15e Mon Sep 17 00:00:00 2001 From: pappnu Date: Thu, 14 Aug 2025 20:14:15 +0300 Subject: [PATCH 08/12] fix(Select): select takes an array of four coordinates --- photoshop/api/_selection.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/photoshop/api/_selection.py b/photoshop/api/_selection.py index ceb8798b..73bcce80 100644 --- a/photoshop/api/_selection.py +++ b/photoshop/api/_selection.py @@ -76,7 +76,12 @@ def cut(self) -> None: def select( self, - region: tuple[float, float, float, float], + region: tuple[ + tuple[float, float], + tuple[float, float], + tuple[float, float], + tuple[float, float], + ], selection_type: SelectionType | None = None, feather: float = 0, anti_alias: bool = True, From 9d8de09f7b4e687d57c0e4bba68f895f3dd32ed2 Mon Sep 17 00:00:00 2001 From: pappnu Date: Fri, 15 Aug 2025 18:19:27 +0300 Subject: [PATCH 09/12] style: Format and sort imports --- photoshop/api/_artlayer.py | 51 ++++++++++---------------- photoshop/api/_artlayers.py | 8 ++-- photoshop/api/_channel.py | 9 +++-- photoshop/api/_channels.py | 8 ++-- photoshop/api/_core.py | 48 +++++++++--------------- photoshop/api/_document.py | 53 +++++++++++++-------------- photoshop/api/_documentinfo.py | 3 +- photoshop/api/_documents.py | 4 +- photoshop/api/_layer.py | 18 +++++---- photoshop/api/_layerComp.py | 5 ++- photoshop/api/_layerComps.py | 12 ++---- photoshop/api/_layerSet.py | 10 +++-- photoshop/api/_layerSets.py | 8 ++-- photoshop/api/_layers.py | 6 ++- photoshop/api/_measurement_log.py | 3 +- photoshop/api/_notifiers.py | 5 +-- photoshop/api/_preferences.py | 32 ++++++++-------- photoshop/api/_selection.py | 26 ++++--------- photoshop/api/_text_fonts.py | 18 +++++---- photoshop/api/action_descriptor.py | 3 +- photoshop/api/action_list.py | 5 ++- photoshop/api/application.py | 31 +++++----------- photoshop/api/base_action.py | 5 +++ photoshop/api/batch_options.py | 6 ++- photoshop/api/collections.py | 18 ++++++--- photoshop/api/enumerations.py | 3 +- photoshop/api/path_item.py | 11 +++++- photoshop/api/path_items.py | 11 +++++- photoshop/api/path_point.py | 5 +++ photoshop/api/path_point_info.py | 1 + photoshop/api/protocols.py | 23 +++++++++--- photoshop/api/save_options/bmp.py | 3 +- photoshop/api/save_options/eps.py | 3 +- photoshop/api/save_options/gif.py | 5 ++- photoshop/api/save_options/jpg.py | 3 +- photoshop/api/save_options/pdf.py | 55 ++++++++-------------------- photoshop/api/save_options/png.py | 4 +- photoshop/api/save_options/tif.py | 8 ++-- photoshop/api/sub_path_info.py | 5 ++- photoshop/api/sub_path_item.py | 8 +++- photoshop/api/text_item.py | 29 ++++++++------- photoshop/session.py | 59 ++++++++++++++---------------- 42 files changed, 314 insertions(+), 317 deletions(-) diff --git a/photoshop/api/_artlayer.py b/photoshop/api/_artlayer.py index 604c7410..e6f71517 100644 --- a/photoshop/api/_artlayer.py +++ b/photoshop/api/_artlayer.py @@ -1,23 +1,22 @@ -# Import local modules +# Import built-in modules from os import PathLike +# Import local modules from photoshop.api._core import Photoshop from photoshop.api._layer import Layer -from photoshop.api.enumerations import ( - CreateFields, - DepthMaource, - DisplacementMapType, - ElementPlacement, - EliminateFields, - Geometry, - LayerKind, - LensType, - NoiseDistribution, - OffsetUndefinedAreas, - RasterizeType, - TextureType, - UndefinedAreas, -) +from photoshop.api.enumerations import CreateFields +from photoshop.api.enumerations import DepthMaource +from photoshop.api.enumerations import DisplacementMapType +from photoshop.api.enumerations import ElementPlacement +from photoshop.api.enumerations import EliminateFields +from photoshop.api.enumerations import Geometry +from photoshop.api.enumerations import LayerKind +from photoshop.api.enumerations import LensType +from photoshop.api.enumerations import NoiseDistribution +from photoshop.api.enumerations import OffsetUndefinedAreas +from photoshop.api.enumerations import RasterizeType +from photoshop.api.enumerations import TextureType +from photoshop.api.enumerations import UndefinedAreas from photoshop.api.text_item import TextItem @@ -290,9 +289,7 @@ def adjustLevels( outputRangeEnd, ) - def applyAddNoise( - self, amount: float, distribution: NoiseDistribution, monochromatic: bool - ) -> None: + def applyAddNoise(self, amount: float, distribution: NoiseDistribution, monochromatic: bool) -> None: self.app.applyAddNoise(amount, distribution, monochromatic) def applyDiffuseGlow(self, graininess: int, amount: int, clear_amount: int) -> None: @@ -324,15 +321,11 @@ def applyClouds(self) -> None: """Applies the clouds filter.""" self.app.applyClouds() - def applyCustomFilter( - self, characteristics: list[int], scale: int, offset: int - ) -> None: + def applyCustomFilter(self, characteristics: list[int], scale: int, offset: int) -> None: """Applies the custom filter.""" self.app.applyCustomFilter(characteristics, scale, offset) - def applyDeInterlace( - self, eliminateFields: EliminateFields, createFields: CreateFields - ) -> None: + def applyDeInterlace(self, eliminateFields: EliminateFields, createFields: CreateFields) -> None: """Applies the de-interlace filter.""" self.app.applyDeInterlace(eliminateFields, createFields) @@ -421,9 +414,7 @@ def applyLensBlur( monochromatic, ) - def applyLensFlare( - self, brightness: int, flareCenter: tuple[float, float], lensType: LensType - ) -> None: + def applyLensFlare(self, brightness: int, flareCenter: tuple[float, float], lensType: LensType) -> None: self.app.applyLensFlare(brightness, flareCenter, lensType) def applyMaximum(self, radius: float) -> None: @@ -444,9 +435,7 @@ def applyNTSC(self) -> None: def applyOceanRipple(self, size: int, magnitude: int) -> None: self.app.applyOceanRipple(size, magnitude) - def applyOffset( - self, horizontal: int, vertical: int, undefinedAreas: OffsetUndefinedAreas - ) -> None: + def applyOffset(self, horizontal: int, vertical: int, undefinedAreas: OffsetUndefinedAreas) -> None: self.app.applyOffset(horizontal, vertical, undefinedAreas) def applyPinch(self, amount: int) -> None: diff --git a/photoshop/api/_artlayers.py b/photoshop/api/_artlayers.py index 6fa08d32..614aaa0b 100644 --- a/photoshop/api/_artlayers.py +++ b/photoshop/api/_artlayers.py @@ -1,11 +1,9 @@ # Import local modules from photoshop.api._artlayer import ArtLayer from photoshop.api._core import Photoshop -from photoshop.api.collections import ( - CollectionOfNamedObjects, - CollectionOfRemovables, - CollectionWithAdd, -) +from photoshop.api.collections import CollectionOfNamedObjects +from photoshop.api.collections import CollectionOfRemovables +from photoshop.api.collections import CollectionWithAdd # pylint: disable=too-many-public-methods diff --git a/photoshop/api/_channel.py b/photoshop/api/_channel.py index c95b3e2d..d4990a7e 100644 --- a/photoshop/api/_channel.py +++ b/photoshop/api/_channel.py @@ -1,11 +1,14 @@ -# Import local modules +# Import built-in modules from typing import TYPE_CHECKING +# Import local modules from photoshop.api._core import Photoshop from photoshop.api.enumerations import ChannelType from photoshop.api.solid_color import SolidColor + if TYPE_CHECKING: + # Import local modules from photoshop.api._document import Document @@ -69,6 +72,4 @@ def merge(self) -> None: self.app.merge() def remove(self) -> None: - self.eval_javascript( - f'app.activeDocument.channels.getByName("{self.name}").remove()' - ) + self.eval_javascript(f'app.activeDocument.channels.getByName("{self.name}").remove()') diff --git a/photoshop/api/_channels.py b/photoshop/api/_channels.py index df5d213b..f41af88e 100644 --- a/photoshop/api/_channels.py +++ b/photoshop/api/_channels.py @@ -1,11 +1,9 @@ # Import local modules from photoshop.api._channel import Channel from photoshop.api._core import Photoshop -from photoshop.api.collections import ( - CollectionOfNamedObjects, - CollectionOfRemovables, - CollectionWithAdd, -) +from photoshop.api.collections import CollectionOfNamedObjects +from photoshop.api.collections import CollectionOfRemovables +from photoshop.api.collections import CollectionWithAdd class Channels( diff --git a/photoshop/api/_core.py b/photoshop/api/_core.py index cd11fc4e..5079517f 100644 --- a/photoshop/api/_core.py +++ b/photoshop/api/_core.py @@ -1,13 +1,17 @@ """This class provides all photoshop API core functions.""" # Import built-in modules +from contextlib import suppress +from functools import cached_property +from logging import CRITICAL +from logging import DEBUG +from logging import Logger +from logging import getLogger import os import platform +from typing import Any +from typing import TYPE_CHECKING import winreg -from contextlib import suppress -from functools import cached_property -from logging import CRITICAL, DEBUG, Logger, getLogger -from typing import TYPE_CHECKING, Any # Import third-party modules from comtypes.client import CreateObject @@ -26,9 +30,7 @@ class Photoshop: _reg_path = "SOFTWARE\\Adobe\\Photoshop" object_name: str = "Application" - def __init__( - self, ps_version: str | None = None, parent: "Photoshop | None" = None - ): + def __init__(self, ps_version: str | None = None, parent: "Photoshop | None" = None): """ Initialize the Photoshop core object. @@ -38,9 +40,7 @@ def __init__( """ # Establish the initial app and program ID ps_version = os.getenv("PS_VERSION", ps_version) - self._app_id = ( - PHOTOSHOP_VERSION_MAPPINGS.get(ps_version, "") if ps_version else "" - ) + self._app_id = PHOTOSHOP_VERSION_MAPPINGS.get(ps_version, "") if ps_version else "" self._has_parent, self.adobe, self.app = False, None, None # Store current photoshop version @@ -62,9 +62,7 @@ def __init__( self.app = self._get_application_object(versions) if not self.app: # All attempts exhausted - raise PhotoshopPythonAPIError( - "Please check if you have Photoshop installed correctly." - ) + raise PhotoshopPythonAPIError("Please check if you have Photoshop installed correctly.") # Add the parent app object if parent: @@ -93,9 +91,7 @@ def __getattribute__(self, name): @cached_property def _debug(self) -> bool: """bool: Enable DEBUG level in logger if PS_DEBUG environment variable is truthy.""" - return bool( - os.getenv("PS_DEBUG", "False").lower() in ["y", "t", "on", "yes", "true"] - ) + return bool(os.getenv("PS_DEBUG", "False").lower() in ["y", "t", "on", "yes", "true"]) @cached_property def _logger(self) -> Logger: @@ -172,14 +168,10 @@ def _get_photoshop_versions(self) -> list[str]: versions = [winreg.EnumKey(key, i).split(".")[0] for i in range(key_count)] # Sort from latest version to oldest, use blank version as a fallback return [*sorted(versions, reverse=True), ""] - self._logger.debug( - "Unable to find Photoshop version number in HKEY_LOCAL_MACHINE registry!" - ) + self._logger.debug("Unable to find Photoshop version number in HKEY_LOCAL_MACHINE registry!") return [] - def _get_application_object( - self, versions: list[str] | None = None - ) -> Dispatch | None: + def _get_application_object(self, versions: list[str] | None = None) -> Dispatch | None: """ Try each version string until a valid Photoshop application Dispatch object is returned. @@ -220,9 +212,7 @@ def get_script_path(self) -> str: """str: The absolute scripts path of Photoshop.""" return os.path.join(self.presets_path, "Scripts") - def eval_javascript( - self, javascript: str, Arguments: Any = None, ExecutionMode: Any = None - ) -> str: + def eval_javascript(self, javascript: str, Arguments: Any = None, ExecutionMode: Any = None) -> str: """Instruct the application to execute javascript code.""" executor = self.adobe if self._has_parent else self.app if executor: @@ -252,13 +242,9 @@ def _open_key(key: str) -> winreg.HKEYType: mappings = {"AMD64": winreg.KEY_WOW64_64KEY} access = winreg.KEY_READ | mappings.get(machine_type, winreg.KEY_WOW64_32KEY) try: - return winreg.OpenKey( - key=winreg.HKEY_LOCAL_MACHINE, sub_key=key, access=access - ) + return winreg.OpenKey(key=winreg.HKEY_LOCAL_MACHINE, sub_key=key, access=access) except FileNotFoundError as err: raise OSError( "Failed to read the registration: <{path}>\n" - "Please check if you have Photoshop installed correctly.".format( - path=f"HKEY_LOCAL_MACHINE\\{key}" - ) + "Please check if you have Photoshop installed correctly.".format(path=f"HKEY_LOCAL_MACHINE\\{key}") ) from err diff --git a/photoshop/api/_document.py b/photoshop/api/_document.py index 370e5066..54321f66 100644 --- a/photoshop/api/_document.py +++ b/photoshop/api/_document.py @@ -16,7 +16,8 @@ # Import built-in modules from os import PathLike from pathlib import Path -from typing import TYPE_CHECKING, Optional +from typing import Optional +from typing import TYPE_CHECKING # Import third-party modules from comtypes import COMError @@ -30,27 +31,27 @@ from photoshop.api._documentinfo import DocumentInfo from photoshop.api._layer import Layer from photoshop.api._layerComps import LayerComps -from photoshop.api._layers import Layers from photoshop.api._layerSet import LayerSet from photoshop.api._layerSets import LayerSets -from photoshop.api.enumerations import ( - AnchorPosition, - BitsPerChannelType, - ChangeMode, - ColorProfileType, - Direction, - DocumentMode, - ExportType, - ExtensionType, - Intent, - MeasurementSource, - ResampleMethod, - SaveOptions, - SourceSpaceType, - TrimType, -) +from photoshop.api._layers import Layers +from photoshop.api.enumerations import AnchorPosition +from photoshop.api.enumerations import BitsPerChannelType +from photoshop.api.enumerations import ChangeMode +from photoshop.api.enumerations import ColorProfileType +from photoshop.api.enumerations import Direction +from photoshop.api.enumerations import DocumentMode +from photoshop.api.enumerations import ExportType +from photoshop.api.enumerations import ExtensionType +from photoshop.api.enumerations import Intent +from photoshop.api.enumerations import MeasurementSource +from photoshop.api.enumerations import ResampleMethod +from photoshop.api.enumerations import SaveOptions +from photoshop.api.enumerations import SourceSpaceType +from photoshop.api.enumerations import TrimType from photoshop.api.path_items import PathItems -from photoshop.api.protocols import HistoryState, MeasurementScale, XMPMetadata +from photoshop.api.protocols import HistoryState +from photoshop.api.protocols import MeasurementScale +from photoshop.api.protocols import XMPMetadata from photoshop.api.save_options import ExportOptionsSaveForWeb from photoshop.api.save_options.bmp import BMPSaveOptions from photoshop.api.save_options.eps import EPSSaveOptions @@ -62,7 +63,9 @@ from photoshop.api.save_options.tag import TargaSaveOptions from photoshop.api.save_options.tif import TiffSaveOptions + if TYPE_CHECKING: + # Import local modules from photoshop.api._selection import Selection @@ -374,9 +377,7 @@ def convertProfile( blackPointCompensation: bool, dither: bool, ) -> None: - self.app.convertProfile( - destinationProfile, intent, blackPointCompensation, dither - ) + self.app.convertProfile(destinationProfile, intent, blackPointCompensation, dither) def flatten(self) -> None: """Flattens all layers.""" @@ -426,9 +427,7 @@ def exportDocument( file_path = file_path.replace("\\", "/") self.app.export(file_path, exportAs, options) - def duplicate( - self, name: str | None = None, merge_layers_only: bool = False - ) -> "Document": + def duplicate(self, name: str | None = None, merge_layers_only: bool = False) -> "Document": return Document(self.app.duplicate(name, merge_layers_only)) def paste(self) -> ArtLayer | LayerSet: @@ -499,9 +498,7 @@ def suspendHistory(self, historyString: str, javaScriptString: str) -> None: Allows a single undo for all actions taken in the script. """ - self.eval_javascript( - f"app.activeDocument.suspendHistory('{historyString}', '{javaScriptString}')" - ) + self.eval_javascript(f"app.activeDocument.suspendHistory('{historyString}', '{javaScriptString}')") def trap(self, width: int) -> None: """ diff --git a/photoshop/api/_documentinfo.py b/photoshop/api/_documentinfo.py index 5c38729b..05f4a1a2 100644 --- a/photoshop/api/_documentinfo.py +++ b/photoshop/api/_documentinfo.py @@ -11,7 +11,8 @@ # Import local modules from photoshop.api._core import Photoshop -from photoshop.api.enumerations import CopyrightedType, Urgency +from photoshop.api.enumerations import CopyrightedType +from photoshop.api.enumerations import Urgency # pylint: disable=too-many-public-methods diff --git a/photoshop/api/_documents.py b/photoshop/api/_documents.py index 09718b8e..84adf63b 100644 --- a/photoshop/api/_documents.py +++ b/photoshop/api/_documents.py @@ -2,7 +2,9 @@ from photoshop.api._core import Photoshop from photoshop.api._document import Document from photoshop.api.collections import CollectionOfNamedObjects -from photoshop.api.enumerations import BitsPerChannelType, DocumentFill, NewDocumentMode +from photoshop.api.enumerations import BitsPerChannelType +from photoshop.api.enumerations import DocumentFill +from photoshop.api.enumerations import NewDocumentMode # pylint: disable=too-many-public-methods, too-many-arguments diff --git a/photoshop/api/_layer.py b/photoshop/api/_layer.py index f9ca6fcb..bfda9c87 100644 --- a/photoshop/api/_layer.py +++ b/photoshop/api/_layer.py @@ -1,10 +1,16 @@ +# Import built-in modules from typing import TYPE_CHECKING +# Import local modules from photoshop.api._core import Photoshop -from photoshop.api.enumerations import AnchorPosition, BlendMode, ElementPlacement +from photoshop.api.enumerations import AnchorPosition +from photoshop.api.enumerations import BlendMode +from photoshop.api.enumerations import ElementPlacement from photoshop.api.protocols import XMPMetadata + if TYPE_CHECKING: + # Import local modules from photoshop.api._document import Document from photoshop.api._layerSet import LayerSet @@ -89,10 +95,12 @@ def parent(self) -> "Document | LayerSet": parent = self.app.parent try: parent.resolution + # Import local modules from photoshop.api._document import Document return Document(parent) except NameError: + # Import local modules from photoshop.api._layerSet import LayerSet return LayerSet(parent) @@ -128,9 +136,7 @@ def duplicate( def link(self, with_layer: "Layer") -> None: self.app.link(with_layer) - def move( - self, relativeObject: "Layer | LayerSet", insertionLocation: ElementPlacement - ) -> None: + def move(self, relativeObject: "Layer | LayerSet", insertionLocation: ElementPlacement) -> None: self.app.move(relativeObject, insertionLocation) def moveToEnd(self, layer_set: "LayerSet") -> None: @@ -140,9 +146,7 @@ def remove(self) -> None: """Removes this layer from the document.""" self.app.delete() - def resize( - self, horizontal: float, vertical: float, anchor: AnchorPosition - ) -> None: + def resize(self, horizontal: float, vertical: float, anchor: AnchorPosition) -> None: """Scales the object.""" self.app.resize(horizontal, vertical, anchor) diff --git a/photoshop/api/_layerComp.py b/photoshop/api/_layerComp.py index 87f66108..a9af259b 100644 --- a/photoshop/api/_layerComp.py +++ b/photoshop/api/_layerComp.py @@ -1,9 +1,12 @@ -# Import local modules +# Import built-in modules from typing import TYPE_CHECKING +# Import local modules from photoshop.api._core import Photoshop + if TYPE_CHECKING: + # Import local modules from photoshop.api._document import Document diff --git a/photoshop/api/_layerComps.py b/photoshop/api/_layerComps.py index c65cf5d6..e453a8d2 100644 --- a/photoshop/api/_layerComps.py +++ b/photoshop/api/_layerComps.py @@ -1,10 +1,8 @@ # Import local modules from photoshop.api._core import Photoshop from photoshop.api._layerComp import LayerComp -from photoshop.api.collections import ( - CollectionOfNamedObjects, - CollectionOfRemovables, -) +from photoshop.api.collections import CollectionOfNamedObjects +from photoshop.api.collections import CollectionOfRemovables class LayerComps( @@ -26,8 +24,4 @@ def add( visibility: bool = True, childLayerCompStat: bool = False, ) -> LayerComp: - return LayerComp( - self.app.add( - name, comment, appearance, position, visibility, childLayerCompStat - ) - ) + return LayerComp(self.app.add(name, comment, appearance, position, visibility, childLayerCompStat)) diff --git a/photoshop/api/_layerSet.py b/photoshop/api/_layerSet.py index 35ef02e4..29e35bb6 100644 --- a/photoshop/api/_layerSet.py +++ b/photoshop/api/_layerSet.py @@ -1,6 +1,8 @@ -# Import local modules -from typing import TYPE_CHECKING, Iterator +# Import built-in modules +from typing import Iterator +from typing import TYPE_CHECKING +# Import local modules from photoshop.api._artlayer import ArtLayer from photoshop.api._artlayers import ArtLayers from photoshop.api._channel import Channel @@ -9,9 +11,11 @@ from photoshop.api._layer import Layer from photoshop.api.enumerations import ElementPlacement + if TYPE_CHECKING: - from photoshop.api._layers import Layers + # Import local modules from photoshop.api._layerSets import LayerSets + from photoshop.api._layers import Layers class LayerSet(Layer): diff --git a/photoshop/api/_layerSets.py b/photoshop/api/_layerSets.py index 1f92544b..98db50eb 100644 --- a/photoshop/api/_layerSets.py +++ b/photoshop/api/_layerSets.py @@ -1,11 +1,9 @@ # Import local modules from photoshop.api._core import Photoshop from photoshop.api._layerSet import LayerSet -from photoshop.api.collections import ( - CollectionOfNamedObjects, - CollectionOfRemovables, - CollectionWithAdd, -) +from photoshop.api.collections import CollectionOfNamedObjects +from photoshop.api.collections import CollectionOfRemovables +from photoshop.api.collections import CollectionWithAdd class LayerSets( diff --git a/photoshop/api/_layers.py b/photoshop/api/_layers.py index 798dd4f3..fe96aff1 100644 --- a/photoshop/api/_layers.py +++ b/photoshop/api/_layers.py @@ -1,6 +1,8 @@ -# Import local modules -from typing import Any, Iterator +# Import built-in modules +from typing import Any +from typing import Iterator +# Import local modules from photoshop.api._artlayer import ArtLayer from photoshop.api._core import Photoshop from photoshop.api._layer import Layer diff --git a/photoshop/api/_measurement_log.py b/photoshop/api/_measurement_log.py index 519ac27c..09201412 100644 --- a/photoshop/api/_measurement_log.py +++ b/photoshop/api/_measurement_log.py @@ -1,6 +1,7 @@ -# Import local modules +# Import built-in modules from typing import Sequence +# Import local modules from photoshop.api._core import Photoshop from photoshop.api.enumerations import MeasurementRange diff --git a/photoshop/api/_notifiers.py b/photoshop/api/_notifiers.py index 735f8bba..c6a6f6ae 100644 --- a/photoshop/api/_notifiers.py +++ b/photoshop/api/_notifiers.py @@ -23,6 +23,7 @@ class Notifiers(CollectionOfRemovables[Notifier, int]): """The `notifiers` currently configured (in the Scripts Events Manager menu in the application).""" if TYPE_CHECKING: + # Import local modules from photoshop.api.application import Application parent: Application @@ -31,9 +32,7 @@ def __init__(self, parent: Photoshop | None = None) -> None: super().__init__(type=Notifier, parent=parent) self._flag_as_method("add") - def add( - self, event: str, event_file: str, event_class: str | None = None - ) -> Notifier: + def add(self, event: str, event_file: str, event_class: str | None = None) -> Notifier: self.parent.notifiersEnabled = True return Notifier(self.app.add(event, event_file, event_class)) diff --git a/photoshop/api/_preferences.py b/photoshop/api/_preferences.py index e70416a6..efab3008 100644 --- a/photoshop/api/_preferences.py +++ b/photoshop/api/_preferences.py @@ -4,23 +4,21 @@ # Import local modules from photoshop.api._core import Photoshop -from photoshop.api.enumerations import ( - ColorPicker, - EditLogItemsType, - FontPreviewType, - FontSize, - GridLineStyle, - GridSize, - GuideLineStyle, - OtherPaintingCursors, - PaintingCursors, - PointType, - QueryStateType, - ResampleMethod, - SaveBehavior, - TypeUnits, - Units, -) +from photoshop.api.enumerations import ColorPicker +from photoshop.api.enumerations import EditLogItemsType +from photoshop.api.enumerations import FontPreviewType +from photoshop.api.enumerations import FontSize +from photoshop.api.enumerations import GridLineStyle +from photoshop.api.enumerations import GridSize +from photoshop.api.enumerations import GuideLineStyle +from photoshop.api.enumerations import OtherPaintingCursors +from photoshop.api.enumerations import PaintingCursors +from photoshop.api.enumerations import PointType +from photoshop.api.enumerations import QueryStateType +from photoshop.api.enumerations import ResampleMethod +from photoshop.api.enumerations import SaveBehavior +from photoshop.api.enumerations import TypeUnits +from photoshop.api.enumerations import Units class Preferences(Photoshop): diff --git a/photoshop/api/_selection.py b/photoshop/api/_selection.py index 73bcce80..99f746e5 100644 --- a/photoshop/api/_selection.py +++ b/photoshop/api/_selection.py @@ -4,12 +4,10 @@ from photoshop.api._channel import Channel from photoshop.api._core import Photoshop from photoshop.api._document import Document -from photoshop.api.enumerations import ( - AnchorPosition, - ColorBlendMode, - SelectionType, - StrokeLocation, -) +from photoshop.api.enumerations import AnchorPosition +from photoshop.api.enumerations import ColorBlendMode +from photoshop.api.enumerations import SelectionType +from photoshop.api.enumerations import StrokeLocation from photoshop.api.solid_color import SolidColor @@ -150,16 +148,12 @@ def makeWorkPath(self, tolerance: float) -> None: """Makes this selection item the workpath for this document.""" self.app.makeWorkPath(tolerance) - def resize( - self, horizontal: float, vertical: float, anchor: AnchorPosition - ) -> None: + def resize(self, horizontal: float, vertical: float, anchor: AnchorPosition) -> None: """Resizes the selected area to the specified dimensions and anchor position.""" self.app.resize(horizontal, vertical, anchor) - def resizeBoundary( - self, horizontal: float, vertical: float, anchor: AnchorPosition - ) -> None: + def resizeBoundary(self, horizontal: float, vertical: float, anchor: AnchorPosition) -> None: """Scales the boundary of the selection.""" self.app.resizeBoundary(horizontal, vertical, anchor) @@ -195,9 +189,7 @@ def stroke( preserveTransparency (bool): If true, preserves transparency. """ - self.app.stroke( - strokeColor, width, location, mode, opacity, preserveTransparency - ) + self.app.stroke(strokeColor, width, location, mode, opacity, preserveTransparency) def selectBorder(self, width: float) -> None: """Selects the selection border only (in the specified width); @@ -217,9 +209,7 @@ def smooth(self, radius: int) -> None: selection (within the radius specified in pixels).""" self.app.smooth(radius) - def store( - self, into: Channel, combination: SelectionType = SelectionType.ReplaceSelection - ) -> None: + def store(self, into: Channel, combination: SelectionType = SelectionType.ReplaceSelection) -> None: """Saves the selection as a channel.""" self.app.store(into, combination) diff --git a/photoshop/api/_text_fonts.py b/photoshop/api/_text_fonts.py index 6cefc525..4f7f008a 100644 --- a/photoshop/api/_text_fonts.py +++ b/photoshop/api/_text_fonts.py @@ -1,14 +1,18 @@ # Import built-in modules -from typing import Iterator, TypeVar, overload +from typing import Iterator +from typing import TypeVar +from typing import overload # Import third-party modules -from comtypes import ArgumentError, COMError +from comtypes import ArgumentError +from comtypes import COMError # Import local modules from photoshop.api._core import Photoshop from photoshop.api.errors import PhotoshopPythonAPIError from photoshop.api.text_font import TextFont + T = TypeVar("T") @@ -63,19 +67,19 @@ def __getitem__(self, key: str) -> TextFont: try: return TextFont(self.app[key]) except ArgumentError: - raise PhotoshopPythonAPIError( - f'Could not find a font with postScriptName "{key}"' - ) + raise PhotoshopPythonAPIError(f'Could not find a font with postScriptName "{key}"') """ METHODS """ @overload - def get(self, key: str, default: T) -> TextFont | T: ... + def get(self, key: str, default: T) -> TextFont | T: + ... @overload - def get(self, key: str) -> TextFont | None: ... + def get(self, key: str) -> TextFont | None: + ... def get(self, key: str, default: T | None = None) -> TextFont | T | None: """ diff --git a/photoshop/api/action_descriptor.py b/photoshop/api/action_descriptor.py index 4b05205d..fe859ca8 100644 --- a/photoshop/api/action_descriptor.py +++ b/photoshop/api/action_descriptor.py @@ -7,9 +7,10 @@ """ -# Import local modules +# Import built-in modules from os import PathLike +# Import local modules from photoshop.api._core import Photoshop from photoshop.api.action_list import ActionList from photoshop.api.action_reference import ActionReference diff --git a/photoshop/api/action_list.py b/photoshop/api/action_list.py index 5299538d..e54a80ff 100644 --- a/photoshop/api/action_list.py +++ b/photoshop/api/action_list.py @@ -5,15 +5,18 @@ """ -# Import local modules +# Import built-in modules from os import PathLike from typing import TYPE_CHECKING +# Import local modules from photoshop.api._core import Photoshop from photoshop.api.action_reference import ActionReference from photoshop.api.base_action import BaseAction + if TYPE_CHECKING: + # Import local modules from photoshop.api.action_descriptor import ActionDescriptor diff --git a/photoshop/api/application.py b/photoshop/api/application.py index 9e8286cb..d7d8a55b 100644 --- a/photoshop/api/application.py +++ b/photoshop/api/application.py @@ -13,12 +13,12 @@ # Import built-in modules import os +from pathlib import Path import time +from typing import Any # Import third-party modules from _ctypes import COMError -from pathlib import Path -from typing import Any # Import local modules from photoshop.api._artlayer import ArtLayer @@ -33,7 +33,8 @@ from photoshop.api.action_descriptor import ActionDescriptor from photoshop.api.action_reference import ActionReference from photoshop.api.batch_options import BatchOptions -from photoshop.api.enumerations import DialogModes, PurgeTarget +from photoshop.api.enumerations import DialogModes +from photoshop.api.enumerations import PurgeTarget from photoshop.api.errors import PhotoshopPythonAPIError from photoshop.api.solid_color import SolidColor @@ -134,9 +135,7 @@ def colorSettings(self, settings: str) -> None: try: self.doJavaScript(f'app.colorSettings="{settings}"') except COMError as e: - raise PhotoshopPythonAPIError( - f"Invalid color profile provided: '{settings}'" - ) from e + raise PhotoshopPythonAPIError(f"Invalid color profile provided: '{settings}'") from e @property def currentTool(self) -> str: @@ -350,9 +349,7 @@ def doProgress(self, title: str, javascript: str) -> None: # Ensure the script execute success. time.sleep(1) - def doProgressSegmentTask( - self, segmentLength: int, done: int, total: int, javascript: str - ) -> None: + def doProgressSegmentTask(self, segmentLength: int, done: int, total: int, javascript: str) -> None: script = "app.doProgressSegmentTask({}, {}, {}, '{}');".format( segmentLength, done, @@ -393,9 +390,7 @@ def executeAction( descriptor: ActionDescriptor | None = None, display_dialogs: DialogModes = DialogModes.DisplayNoDialogs, ) -> ActionDescriptor: - return ActionDescriptor( - self.app.executeAction(event_id, descriptor, display_dialogs) - ) + return ActionDescriptor(self.app.executeAction(event_id, descriptor, display_dialogs)) def executeActionGet(self, reference: ActionReference) -> ActionDescriptor: return ActionDescriptor(self.app.executeActionGet(reference)) @@ -425,9 +420,7 @@ def open( document_type: str | None = None, as_smart_object: bool = False, ) -> Document: - document = self.app.open( - str(document_file_path), document_type, as_smart_object - ) + document = self.app.open(str(document_file_path), document_type, as_smart_object) if not as_smart_object: return Document(document) return document @@ -437,9 +430,7 @@ def load(self, document_file_path: str | os.PathLike[str]) -> Document: self.app.load(str(document_file_path)) return self.activeDocument - def doJavaScript( - self, javascript: str, Arguments: Any = None, ExecutionMode: Any = None - ): + def doJavaScript(self, javascript: str, Arguments: Any = None, ExecutionMode: Any = None): return self.app.doJavaScript(javascript, Arguments, ExecutionMode) def isQuicktimeAvailable(self) -> bool: @@ -461,9 +452,7 @@ def purge(self, target: PurgeTarget) -> None: """ self.app.purge(target) - def putCustomOptions( - self, key: str, custom_object: ActionDescriptor, persistent: bool - ) -> None: + def putCustomOptions(self, key: str, custom_object: ActionDescriptor, persistent: bool) -> None: self.app.putCustomOptions(key, custom_object, persistent) def refresh(self) -> None: diff --git a/photoshop/api/base_action.py b/photoshop/api/base_action.py index 3f1f71c1..4aa2b201 100644 --- a/photoshop/api/base_action.py +++ b/photoshop/api/base_action.py @@ -1,11 +1,15 @@ +# Import built-in modules from pathlib import Path from typing import TYPE_CHECKING +# Import local modules from photoshop.api._core import Photoshop from photoshop.api.action_reference import ActionReference from photoshop.api.enumerations import DescValueType + if TYPE_CHECKING: + # Import local modules from photoshop.api.action_descriptor import ActionDescriptor from photoshop.api.action_list import ActionList @@ -98,6 +102,7 @@ def getLargeInteger(self, index: int) -> int: def getList(self, index: int) -> "ActionList": """Gets the value of a key of type list.""" + # Import local modules from photoshop.api.action_list import ActionList return ActionList(self.app.getList(index)) diff --git a/photoshop/api/batch_options.py b/photoshop/api/batch_options.py index f679241a..b7e0fb33 100644 --- a/photoshop/api/batch_options.py +++ b/photoshop/api/batch_options.py @@ -1,11 +1,13 @@ # https://theiviaxx.github.io/photoshop-docs/Photoshop/BatchOptions.html -# Import local modules +# Import built-in modules from os import PathLike from pathlib import Path from typing import Sequence +# Import local modules from photoshop.api._core import Photoshop -from photoshop.api.enumerations import BatchDestinationType, FileNamingType +from photoshop.api.enumerations import BatchDestinationType +from photoshop.api.enumerations import FileNamingType class BatchOptions(Photoshop): diff --git a/photoshop/api/collections.py b/photoshop/api/collections.py index 1a32f837..d31e2e5d 100644 --- a/photoshop/api/collections.py +++ b/photoshop/api/collections.py @@ -1,18 +1,26 @@ -from typing import Generic, Iterator, Protocol, TypeVar +# Import built-in modules +from typing import Generic +from typing import Iterator +from typing import Protocol +from typing import TypeVar +# Import third-party modules from comtypes import ArgumentError +# Import local modules from photoshop.api._core import Photoshop from photoshop.api.errors import PhotoshopPythonAPIError class _PhotoshopObject(Protocol): - def __init__(self, parent: Photoshop | None = None) -> None: ... + def __init__(self, parent: Photoshop | None = None) -> None: + ... class NamedPhotoshopObject(_PhotoshopObject, Protocol): @property - def name(self) -> str: ... + def name(self) -> str: + ... T = TypeVar("T", bound=_PhotoshopObject) @@ -39,9 +47,7 @@ def __getitem__(self, key: G) -> T: try: return self.type(self.app[key]) except ArgumentError as exc: - raise PhotoshopPythonAPIError( - f"Couldn't find an item with key '{key}' in {type(self)}" - ) from exc + raise PhotoshopPythonAPIError(f"Couldn't find an item with key '{key}' in {type(self)}") from exc def __iter__(self) -> Iterator[T]: for item in self.app: diff --git a/photoshop/api/enumerations.py b/photoshop/api/enumerations.py index 3ee97361..97fbfd01 100644 --- a/photoshop/api/enumerations.py +++ b/photoshop/api/enumerations.py @@ -1,6 +1,7 @@ """constants type of enum for Photoshop.""" # Import built-in modules -from enum import IntEnum, StrEnum +from enum import IntEnum +from enum import StrEnum class LensType(IntEnum): diff --git a/photoshop/api/path_item.py b/photoshop/api/path_item.py index ae11d8fd..9af47db1 100644 --- a/photoshop/api/path_item.py +++ b/photoshop/api/path_item.py @@ -1,13 +1,21 @@ +# Import built-in modules from typing import Iterator +# Import third-party modules from git import TYPE_CHECKING +# Import local modules from photoshop.api._core import Photoshop -from photoshop.api.enumerations import ColorBlendMode, PathKind, SelectionType, ToolType +from photoshop.api.enumerations import ColorBlendMode +from photoshop.api.enumerations import PathKind +from photoshop.api.enumerations import SelectionType +from photoshop.api.enumerations import ToolType from photoshop.api.solid_color import SolidColor from photoshop.api.sub_path_item import SubPathItem + if TYPE_CHECKING: + # Import local modules from photoshop.api._document import Document @@ -43,6 +51,7 @@ def name(self, value: str) -> None: @property def parent(self) -> "Document": + # Import local modules from photoshop.api._document import Document return Document(self.app.parent) diff --git a/photoshop/api/path_items.py b/photoshop/api/path_items.py index 626d2dfa..aa59ff22 100644 --- a/photoshop/api/path_items.py +++ b/photoshop/api/path_items.py @@ -1,11 +1,17 @@ -from typing import TYPE_CHECKING, Sequence +# Import built-in modules +from typing import Sequence +from typing import TYPE_CHECKING +# Import local modules from photoshop.api._core import Photoshop -from photoshop.api.collections import CollectionOfNamedObjects, CollectionOfRemovables +from photoshop.api.collections import CollectionOfNamedObjects +from photoshop.api.collections import CollectionOfRemovables from photoshop.api.path_item import PathItem from photoshop.api.sub_path_info import SubPathInfo + if TYPE_CHECKING: + # Import local modules from photoshop.api._document import Document @@ -22,6 +28,7 @@ def add(self, name: str, entire_path: Sequence[SubPathInfo]) -> PathItem: @property def parent(self) -> "Document": + # Import local modules from photoshop.api._document import Document return Document(self.app.parent) diff --git a/photoshop/api/path_point.py b/photoshop/api/path_point.py index a9504ad8..9a09984d 100644 --- a/photoshop/api/path_point.py +++ b/photoshop/api/path_point.py @@ -1,9 +1,13 @@ +# Import built-in modules from typing import TYPE_CHECKING +# Import local modules from photoshop.api._core import Photoshop from photoshop.api.enumerations import PointKind + if TYPE_CHECKING: + # Import local modules from photoshop.api.sub_path_item import SubPathItem @@ -25,6 +29,7 @@ def leftDirection(self) -> tuple[float, float]: @property def parent(self) -> "SubPathItem": + # Import local modules from photoshop.api.sub_path_item import SubPathItem return SubPathItem(self.app.parent) diff --git a/photoshop/api/path_point_info.py b/photoshop/api/path_point_info.py index 2920b5ad..5dad6bf0 100644 --- a/photoshop/api/path_point_info.py +++ b/photoshop/api/path_point_info.py @@ -1,3 +1,4 @@ +# Import local modules from photoshop.api._core import Photoshop from photoshop.api.enumerations import PointKind diff --git a/photoshop/api/protocols.py b/photoshop/api/protocols.py index a88b88b8..989b155c 100644 --- a/photoshop/api/protocols.py +++ b/photoshop/api/protocols.py @@ -1,21 +1,31 @@ -from typing import TYPE_CHECKING, Protocol +# Import built-in modules +from typing import Protocol +from typing import TYPE_CHECKING + if TYPE_CHECKING: + # Import local modules from photoshop.api._document import Document class BaseProtocol(Protocol): @property - def typename(self) -> str: ... + def typename(self) -> str: + ... class HistoryState(BaseProtocol, Protocol): @property - def name(self) -> str: ... + def name(self) -> str: + ... + @property - def parent(self) -> "Document": ... + def parent(self) -> "Document": + ... + @property - def snapshot(self) -> bool: ... + def snapshot(self) -> bool: + ... class MeasurementScale(BaseProtocol, Protocol): @@ -26,6 +36,7 @@ class MeasurementScale(BaseProtocol, Protocol): class XMPMetadata(BaseProtocol, Protocol): @property - def parent(self) -> "Document": ... + def parent(self) -> "Document": + ... rawData: str diff --git a/photoshop/api/save_options/bmp.py b/photoshop/api/save_options/bmp.py index c7dbf198..6493e00a 100644 --- a/photoshop/api/save_options/bmp.py +++ b/photoshop/api/save_options/bmp.py @@ -2,7 +2,8 @@ # Import local modules from photoshop.api._core import Photoshop -from photoshop.api.enumerations import BMPDepthType, OperatingSystem +from photoshop.api.enumerations import BMPDepthType +from photoshop.api.enumerations import OperatingSystem class BMPSaveOptions(Photoshop): diff --git a/photoshop/api/save_options/eps.py b/photoshop/api/save_options/eps.py index 0561e49e..ba69fa25 100644 --- a/photoshop/api/save_options/eps.py +++ b/photoshop/api/save_options/eps.py @@ -1,6 +1,7 @@ # Import local modules from photoshop.api._core import Photoshop -from photoshop.api.enumerations import PreviewType, SaveEncoding +from photoshop.api.enumerations import PreviewType +from photoshop.api.enumerations import SaveEncoding class EPSSaveOptions(Photoshop): diff --git a/photoshop/api/save_options/gif.py b/photoshop/api/save_options/gif.py index 7a8765c2..3eaff8fb 100644 --- a/photoshop/api/save_options/gif.py +++ b/photoshop/api/save_options/gif.py @@ -1,6 +1,9 @@ # Import local modules from photoshop.api._core import Photoshop -from photoshop.api.enumerations import DitherType, ForcedColors, MatteType, PaletteType +from photoshop.api.enumerations import DitherType +from photoshop.api.enumerations import ForcedColors +from photoshop.api.enumerations import MatteType +from photoshop.api.enumerations import PaletteType class GIFSaveOptions(Photoshop): diff --git a/photoshop/api/save_options/jpg.py b/photoshop/api/save_options/jpg.py index 3decb05b..c2d7d0e9 100644 --- a/photoshop/api/save_options/jpg.py +++ b/photoshop/api/save_options/jpg.py @@ -1,6 +1,7 @@ # Import local modules from photoshop.api._core import Photoshop -from photoshop.api.enumerations import FormatOptionsType, MatteType +from photoshop.api.enumerations import FormatOptionsType +from photoshop.api.enumerations import MatteType class JPEGSaveOptions(Photoshop): diff --git a/photoshop/api/save_options/pdf.py b/photoshop/api/save_options/pdf.py index 0602164b..28dc772a 100644 --- a/photoshop/api/save_options/pdf.py +++ b/photoshop/api/save_options/pdf.py @@ -6,12 +6,10 @@ # Import local modules from photoshop.api._core import Photoshop -from photoshop.api.enumerations import ( - PDFCompatibilityType, - PDFEncodingType, - PDFResampleType, - PDFStandardType, -) +from photoshop.api.enumerations import PDFCompatibilityType +from photoshop.api.enumerations import PDFEncodingType +from photoshop.api.enumerations import PDFResampleType +from photoshop.api.enumerations import PDFStandardType from photoshop.api.errors import COMError @@ -150,9 +148,7 @@ def destinationProfile(self) -> str: return self.app.destinationProfile except COMError: raise ValueError( - "Should set value first. " - "This parameter can only be read after the " - "value has been set." + "Should set value first. " "This parameter can only be read after the " "value has been set." ) @destinationProfile.setter @@ -179,9 +175,7 @@ def downSampleSize(self) -> float: return self.app.downSampleSize except COMError: raise ValueError( - "Should set value first. " - "This parameter can only be read after the " - "value has been set." + "Should set value first. " "This parameter can only be read after the " "value has been set." ) @downSampleSize.setter @@ -198,9 +192,7 @@ def downSampleSizeLimit(self) -> float: return self.app.downSampleSizeLimit except COMError: raise ValueError( - "Should set value first. " - "This parameter can only be read after the " - "value has been set." + "Should set value first. " "This parameter can only be read after the " "value has been set." ) @downSampleSizeLimit.setter @@ -295,9 +287,7 @@ def outputConditionID(self) -> str: return self.app.outputConditionID except COMError: raise ValueError( - "Should set value first. " - "This parameter can only be read after the " - "value has been set." + "Should set value first. " "This parameter can only be read after the " "value has been set." ) @outputConditionID.setter @@ -329,9 +319,7 @@ def preserveEditing(self) -> bool: return self.app.preserveEditing except COMError: raise ValueError( - "Should set value first. " - "This parameter can only be read after the " - "value has been set." + "Should set value first. " "This parameter can only be read after the " "value has been set." ) @preserveEditing.setter @@ -347,9 +335,7 @@ def presetFile(self) -> str: return self.app.presetFile except COMError: raise ValueError( - "Should set value first. " - "This parameter can only be read after the " - "value has been set." + "Should set value first. " "This parameter can only be read after the " "value has been set." ) @presetFile.setter @@ -364,9 +350,7 @@ def profileInclusionPolicy(self) -> bool: return self.app.profileInclusionPolicy except COMError: raise ValueError( - "Should set value first. " - "This parameter can only be read after the " - "value has been set." + "Should set value first. " "This parameter can only be read after the " "value has been set." ) @profileInclusionPolicy.setter @@ -381,9 +365,7 @@ def registryName(self) -> str: return self.app.registryName except COMError: raise ValueError( - "Should set value first. " - "This parameter can only be read after the " - "value has been set." + "Should set value first. " "This parameter can only be read after the " "value has been set." ) @registryName.setter @@ -398,9 +380,7 @@ def spotColors(self) -> bool: return self.app.spotColors except COMError: raise ValueError( - "Should set value first. " - "This parameter can only be read after the " - "value has been set." + "Should set value first. " "This parameter can only be read after the " "value has been set." ) @spotColors.setter @@ -415,9 +395,7 @@ def tileSize(self) -> int: return self.app.tileSize except COMError: raise ValueError( - "Should set value first. " - "This parameter can only be read after the " - "value has been set." + "Should set value first. " "This parameter can only be read after the " "value has been set." ) @tileSize.setter @@ -431,10 +409,7 @@ def tileSize(self, value: int) -> None: PDFEncodingType.PDFJPEG2000LOW, PDFEncodingType.PDFJPEG2000MEDHIGH, ): - raise ValueError( - "tileSize only work in JPEG2000. Please " - "change PDFSaveOptions.encoding to JPEG2000." - ) + raise ValueError("tileSize only work in JPEG2000. Please " "change PDFSaveOptions.encoding to JPEG2000.") self.app.tileSize = value @property diff --git a/photoshop/api/save_options/png.py b/photoshop/api/save_options/png.py index 3fbdc08d..bd5c45ad 100644 --- a/photoshop/api/save_options/png.py +++ b/photoshop/api/save_options/png.py @@ -1,7 +1,9 @@ # Import local modules from photoshop.api._core import Photoshop from photoshop.api.colors.rgb import RGBColor -from photoshop.api.enumerations import ColorReductionType, DitherType, SaveDocumentType +from photoshop.api.enumerations import ColorReductionType +from photoshop.api.enumerations import DitherType +from photoshop.api.enumerations import SaveDocumentType class ExportOptionsSaveForWeb(Photoshop): diff --git a/photoshop/api/save_options/tif.py b/photoshop/api/save_options/tif.py index 32b29ad8..566dcf1f 100644 --- a/photoshop/api/save_options/tif.py +++ b/photoshop/api/save_options/tif.py @@ -1,10 +1,8 @@ # Import local modules from photoshop.api._core import Photoshop -from photoshop.api.enumerations import ( - ByteOrderType, - LayerCompressionType, - TiffEncodingType, -) +from photoshop.api.enumerations import ByteOrderType +from photoshop.api.enumerations import LayerCompressionType +from photoshop.api.enumerations import TiffEncodingType class TiffSaveOptions(Photoshop): diff --git a/photoshop/api/sub_path_info.py b/photoshop/api/sub_path_info.py index 1e0b81eb..e13aa5c5 100644 --- a/photoshop/api/sub_path_info.py +++ b/photoshop/api/sub_path_info.py @@ -1,5 +1,8 @@ -from typing import Iterator, Sequence +# Import built-in modules +from typing import Iterator +from typing import Sequence +# Import local modules from photoshop.api._core import Photoshop from photoshop.api.enumerations import ShapeOperation from photoshop.api.path_point_info import PathPointInfo diff --git a/photoshop/api/sub_path_item.py b/photoshop/api/sub_path_item.py index 95c5ecd5..95e62dff 100644 --- a/photoshop/api/sub_path_item.py +++ b/photoshop/api/sub_path_item.py @@ -1,10 +1,15 @@ -from typing import TYPE_CHECKING, Iterator +# Import built-in modules +from typing import Iterator +from typing import TYPE_CHECKING +# Import local modules from photoshop.api._core import Photoshop from photoshop.api.enumerations import ShapeOperation from photoshop.api.path_point import PathPoint + if TYPE_CHECKING: + # Import local modules from photoshop.api.path_item import PathItem @@ -22,6 +27,7 @@ def operation(self) -> ShapeOperation: @property def parent(self) -> "PathItem": + # Import local modules from photoshop.api.path_item import PathItem return PathItem(self.app.parent) diff --git a/photoshop/api/text_item.py b/photoshop/api/text_item.py index 5281099f..2a0dba85 100644 --- a/photoshop/api/text_item.py +++ b/photoshop/api/text_item.py @@ -1,23 +1,24 @@ -# Import local modules +# Import built-in modules from typing import TYPE_CHECKING +# Import local modules from photoshop.api._core import Photoshop -from photoshop.api.enumerations import ( - AntiAlias, - AutoKernType, - Case, - Direction, - Justification, - Language, - StrikeThruType, - TextComposer, - TextType, - UnderlineType, - WarpStyle, -) +from photoshop.api.enumerations import AntiAlias +from photoshop.api.enumerations import AutoKernType +from photoshop.api.enumerations import Case +from photoshop.api.enumerations import Direction +from photoshop.api.enumerations import Justification +from photoshop.api.enumerations import Language +from photoshop.api.enumerations import StrikeThruType +from photoshop.api.enumerations import TextComposer +from photoshop.api.enumerations import TextType +from photoshop.api.enumerations import UnderlineType +from photoshop.api.enumerations import WarpStyle from photoshop.api.solid_color import SolidColor + if TYPE_CHECKING: + # Import local modules from photoshop.api._artlayer import ArtLayer diff --git a/photoshop/session.py b/photoshop/session.py index 41cb67e7..eb5f0588 100644 --- a/photoshop/session.py +++ b/photoshop/session.py @@ -30,36 +30,35 @@ from contextlib import AbstractContextManager from os import PathLike from types import TracebackType -from typing import Any, Literal +from typing import Any +from typing import Literal # Import local modules -from photoshop.api import ( - ActionDescriptor, - ActionList, - ActionReference, - Application, - BatchOptions, - BMPSaveOptions, - CMYKColor, - EPSSaveOptions, - EventID, - ExportOptionsSaveForWeb, - GIFSaveOptions, - GrayColor, - HSBColor, - JPEGSaveOptions, - LabColor, - PDFSaveOptions, - PhotoshopSaveOptions, - PNGSaveOptions, - RGBColor, - SolidColor, - TargaSaveOptions, - TextItem, - TiffSaveOptions, - enumerations, - errors, -) +from photoshop.api import ActionDescriptor +from photoshop.api import ActionList +from photoshop.api import ActionReference +from photoshop.api import Application +from photoshop.api import BMPSaveOptions +from photoshop.api import BatchOptions +from photoshop.api import CMYKColor +from photoshop.api import EPSSaveOptions +from photoshop.api import EventID +from photoshop.api import ExportOptionsSaveForWeb +from photoshop.api import GIFSaveOptions +from photoshop.api import GrayColor +from photoshop.api import HSBColor +from photoshop.api import JPEGSaveOptions +from photoshop.api import LabColor +from photoshop.api import PDFSaveOptions +from photoshop.api import PNGSaveOptions +from photoshop.api import PhotoshopSaveOptions +from photoshop.api import RGBColor +from photoshop.api import SolidColor +from photoshop.api import TargaSaveOptions +from photoshop.api import TextItem +from photoshop.api import TiffSaveOptions +from photoshop.api import enumerations +from photoshop.api import errors from photoshop.api._document import Document @@ -204,9 +203,7 @@ def __init__( self.GalleryConstrainType = enumerations.GalleryConstrainType self.GalleryFontType = enumerations.GalleryFontType self.GallerySecurityTextColorType = enumerations.GallerySecurityTextColorType - self.GallerySecurityTextPositionType = ( - enumerations.GallerySecurityTextPositionType - ) + self.GallerySecurityTextPositionType = enumerations.GallerySecurityTextPositionType self.GallerySecurityTextRotateType = enumerations.GallerySecurityTextRotateType self.GallerySecurityType = enumerations.GallerySecurityType self.GalleryThumbSizeType = enumerations.GalleryThumbSizeType From 1a7b953eb4dd96fc7526d49d0daddbf45a01ed34 Mon Sep 17 00:00:00 2001 From: pappnu Date: Fri, 15 Aug 2025 18:20:44 +0300 Subject: [PATCH 10/12] feat(Layer): Add default value to rotate anchor --- photoshop/api/_layer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/photoshop/api/_layer.py b/photoshop/api/_layer.py index bfda9c87..28c81bfb 100644 --- a/photoshop/api/_layer.py +++ b/photoshop/api/_layer.py @@ -150,7 +150,7 @@ def resize(self, horizontal: float, vertical: float, anchor: AnchorPosition) -> """Scales the object.""" self.app.resize(horizontal, vertical, anchor) - def rotate(self, angle: float, anchor: AnchorPosition) -> None: + def rotate(self, angle: float, anchor: AnchorPosition = AnchorPosition.MiddleCenter) -> None: return self.app.rotate(angle, anchor) def translate(self, deltaX: float, deltaY: float) -> None: From e790d52e8aa4a753d2a1b6044afe062af545e4c4 Mon Sep 17 00:00:00 2001 From: pappnu Date: Sat, 16 Aug 2025 19:00:20 +0300 Subject: [PATCH 11/12] fix(Documents): Document width and height can be given as floats, though they are converted to integers --- photoshop/api/_documents.py | 10 ++++----- test/manual_test/manual_test_documents.py | 25 +++++++++++++++++++++++ 2 files changed, 30 insertions(+), 5 deletions(-) create mode 100644 test/manual_test/manual_test_documents.py diff --git a/photoshop/api/_documents.py b/photoshop/api/_documents.py index 84adf63b..86d10d25 100644 --- a/photoshop/api/_documents.py +++ b/photoshop/api/_documents.py @@ -17,8 +17,8 @@ def __init__(self, parent: Photoshop | None = None) -> None: def add( self, - width: int = 960, - height: int = 540, + width: float = 960, + height: float = 540, resolution: float = 72.0, name: str | None = None, mode: NewDocumentMode = NewDocumentMode.NewRGB, @@ -30,9 +30,9 @@ def add( """Creates a new document object and adds it to this collections. Args: - width (int): The width of the document. - height (int): The height of the document. - resolution (int): The resolution of the document (in pixels per inch) + width (float): The width of the document. Non-integer values are converted to integers. + height (float): The height of the document. Non-integer values are converted to integers. + resolution (float): The resolution of the document (in pixels per inch) name (str): The name of the document. mode (): The document mode. initialFill : The initial fill of the document. diff --git a/test/manual_test/manual_test_documents.py b/test/manual_test/manual_test_documents.py new file mode 100644 index 00000000..e73234e0 --- /dev/null +++ b/test/manual_test/manual_test_documents.py @@ -0,0 +1,25 @@ +from math import isclose +import pytest + +from photoshop.api.application import Application + + +class TestNewDocument: + """Test various parts of the API in a new document.""" + + @pytest.fixture(autouse=True) + def setup(self): + """Setup for current test.""" + self.app = Application() + self.docs = self.app.documents + + def test_create_document(self): + doc_name = "test_document" + w = 22.3 + h = 47 + res = 55.8 + doc = self.docs.add(22.3, 47, resolution=55.8, name=doc_name) + assert doc.name == doc_name + assert doc.width == int(w) + assert doc.height == h + assert isclose(doc.resolution, res, abs_tol=0.01) From 9f8496ef88c96918a8ba4434f5878968e034fcaa Mon Sep 17 00:00:00 2001 From: pappnu Date: Tue, 26 Aug 2025 20:56:36 +0300 Subject: [PATCH 12/12] fix(path_item.py): Import TYPE_CHECKING from typing instead of erroneously from git --- photoshop/api/path_item.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/photoshop/api/path_item.py b/photoshop/api/path_item.py index 9af47db1..cbfa66aa 100644 --- a/photoshop/api/path_item.py +++ b/photoshop/api/path_item.py @@ -1,15 +1,9 @@ # Import built-in modules -from typing import Iterator - -# Import third-party modules -from git import TYPE_CHECKING +from typing import TYPE_CHECKING, Iterator # Import local modules from photoshop.api._core import Photoshop -from photoshop.api.enumerations import ColorBlendMode -from photoshop.api.enumerations import PathKind -from photoshop.api.enumerations import SelectionType -from photoshop.api.enumerations import ToolType +from photoshop.api.enumerations import ColorBlendMode, PathKind, SelectionType, ToolType from photoshop.api.solid_color import SolidColor from photoshop.api.sub_path_item import SubPathItem