diff --git a/.github/workflows/code-check.yml b/.github/workflows/code-check.yml new file mode 100644 index 00000000..cfce1631 --- /dev/null +++ b/.github/workflows/code-check.yml @@ -0,0 +1,58 @@ +name: Code Quality Check + +on: [pull_request] + +# Add concurrency configuration +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.9' + cache: 'pip' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements-dev.txt + + - name: Run linting checks + run: | + nox -s pre-commit ruff mypy + + test: + runs-on: windows-latest + needs: lint # Add dependency on lint job + strategy: + matrix: + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + fail-fast: false # Continue with other versions if one fails + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: 'pip' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements-dev.txt + + - name: Run tests + run: | + nox -s tests diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 00000000..b2314944 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,58 @@ +name: Documentation + +on: + push: + branches: [ main ] + paths: + - 'docs/**' + - 'mkdocs.yml' + - '.github/workflows/docs.yml' + - 'pyproject.toml' + - 'requirements*.txt' + - 'noxfile.py' + pull_request: + branches: [ main ] + paths: + - 'docs/**' + - 'mkdocs.yml' + - '.github/workflows/docs.yml' + - 'pyproject.toml' + - 'requirements*.txt' + - 'noxfile.py' + +jobs: + docs: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.9' + cache: 'pip' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements-dev.txt + + - name: Configure Git + if: github.event_name == 'push' + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + + - name: Build documentation + if: github.event_name == 'pull_request' + run: | + nox -s docs -- build + + - name: Deploy documentation + if: github.event_name == 'push' + run: | + nox -s docs -- deploy diff --git a/.github/workflows/import-test.yml b/.github/workflows/import-test.yml deleted file mode 100644 index af769fcb..00000000 --- a/.github/workflows/import-test.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: Import Test -on: [pull_request] - -jobs: - python-check: - runs-on: windows-2022 - strategy: - max-parallel: 3 - matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] - - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - with: - fetch-depth: 0 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install -U pip poetry - poetry --version - poetry install -vvv || poetry install -vvv || poetry install -vvv - poetry run pytest diff --git a/.github/workflows/publish_docs.yml b/.github/workflows/publish_docs.yml deleted file mode 100644 index 4ba1b30c..00000000 --- a/.github/workflows/publish_docs.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Documentation - -on: - push: - branches: - - main - -jobs: - docs: - runs-on: ubuntu-latest - permissions: - # IMPORTANT: this permission is mandatory for trusted publishing - id-token: write - contents: write - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - with: - fetch-depth: 0 - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.10' - - run: pip install --upgrade pip && python -m pip install -U pip poetry - - run: poetry --version - - run: poetry install - - name: Publish docs - run: | - gh --version - poetry run mkdocs gh-deploy --remote-branch gh-pages diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index c449912f..46e59f75 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -5,6 +5,11 @@ on: tags: - "v*" +# Add concurrency configuration +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: deploy: runs-on: ubuntu-latest @@ -14,30 +19,37 @@ jobs: contents: write steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - uses: actions/checkout@v4 with: token: "${{ secrets.GITHUB_TOKEN }}" fetch-depth: 0 ref: main + - uses: olegtarasov/get-tag@v2.1.3 id: get_tag_name with: tagRegex: "v(?.*)" + - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.x' + python-version: '3.9' + cache: 'pip' + - name: Install dependencies run: | - python -m pip install -U pip poetry mkdocs mkdocs-material - poetry --version - poetry build - # Note that we don't need credentials. - # We rely on https://docs.pypi.org/trusted-publishers/. + python -m pip install --upgrade pip + pip install -r requirements-dev.txt + + - name: Build package + run: | + nox -s build + - name: Upload to PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: packages-dir: dist + - name: Generate changelog id: changelog uses: jaywcjlove/changelog-generator@main @@ -50,12 +62,23 @@ jobs: {{fix}} ## Feature {{feat}} - ## Improve - {{refactor,perf,clean}} - ## Misc - {{chore,style,ci||🔶 Nothing change}} - ## Unknown - {{__unknown__}} + ## Breaking Changes + {{breaking}} + ## Updates + {{chore}} + ## Document + {{docs}} + ## Dependency + {{deps}} + ## Style + {{style}} + ## Test + {{test}} + ## CI + {{ci}} + ## Other + {{other}} + - uses: ncipollo/release-action@v1 with: artifacts: "dist/*.whl" diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml deleted file mode 100644 index a5aadb12..00000000 --- a/.github/workflows/pythonpackage.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: Python package -on: [pull_request] - -jobs: - python-check: - runs-on: ubuntu-latest - strategy: - max-parallel: 3 - matrix: - python-version: ["3.8", "3.9", "3.10"] - - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - with: - fetch-depth: 0 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install -U pip poetry - poetry --version - poetry install - - - name: Run tests and linters - run: | - #!/bin/sh -e - git config --global user.email "action@github.com" - git config --global user.name "GitHub Action" - export PREFIX="poetry run python -m " - if [ -d 'venv' ] ; then - export PREFIX="venv/bin/" - fi - - ${PREFIX}black photoshop --check - ${PREFIX}isort --check-only photoshop - ${PREFIX}flake8 photoshop --max-line-length 120 diff --git a/.gitignore b/.gitignore index 592ac66a..145879ee 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,14 @@ # Vim / Notepad++ temp files *~ -.*/ + +# Specific dot directories to ignore (but not .github) +.pytest_cache/ +.mypy_cache/ +.tox/ +.venv/ +.coverage/ + *.egg-info # PyInstaller output build/ @@ -24,3 +31,13 @@ venv_python # Docs docs_src/_build/ +/run_pycharm.bat + + +# windsurf rules +.windsurfrules +/examples/files/bg/ +/examples/files/blue/ +/examples/files/green/ +/examples/files/red/ +/site/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a27e4468..87b94235 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,11 +1,14 @@ exclude: conf.py repos: -- repo: https://github.com/ambv/black - rev: 22.12.0 +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.1.9 + hooks: + - id: ruff + args: [--fix] + - id: ruff-format +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 hooks: - - id: black - language_version: python3.10 -- hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: check-docstring-first @@ -14,43 +17,52 @@ repos: - id: debug-statements - id: name-tests-test - id: requirements-txt-fixer - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v2.4.0 -- hooks: + - id: check-added-large-files + - id: check-merge-conflict +- repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.7.1 + hooks: + - id: mypy + additional_dependencies: [types-all] +- repo: https://github.com/asottile/pyupgrade + rev: v3.15.0 + hooks: + - id: pyupgrade + args: [--py37-plus] +- repo: https://github.com/ambv/black + rev: 22.12.0 + hooks: + - id: black + language_version: python3.10 +- repo: https://github.com/pycqa/flake8 + rev: 3.7.9 + hooks: - additional_dependencies: - flake8-typing-imports==1.5.0 id: flake8 - repo: https://github.com/pycqa/flake8 - rev: 3.7.9 -- hooks: - - id: autopep8 - repo: https://github.com/pre-commit/mirrors-autopep8 +- repo: https://github.com/pre-commit/mirrors-autopep8 rev: v1.4.4 -- hooks: - - id: validate_manifest - repo: https://github.com/pre-commit/pre-commit + hooks: + - id: autopep8 +- repo: https://github.com/pre-commit/pre-commit rev: v1.21.0 -- hooks: - - args: - - --py36-plus - id: pyupgrade - repo: https://github.com/asottile/pyupgrade - rev: v1.25.3 -- hooks: + hooks: + - id: validate_manifest +- repo: https://github.com/asottile/reorder_python_imports + rev: v1.9.0 + hooks: - args: - --py3-plus id: reorder-python-imports - repo: https://github.com/asottile/reorder_python_imports - rev: v1.9.0 -- hooks: +- repo: https://github.com/asottile/add-trailing-comma + rev: v1.5.0 + hooks: - args: - --py36-plus id: add-trailing-comma - repo: https://github.com/asottile/add-trailing-comma - rev: v1.5.0 -- hooks: +- repo: https://github.com/commitizen-tools/commitizen + rev: v2.17.8 + hooks: - id: commitizen stages: - commit-msg - repo: https://github.com/commitizen-tools/commitizen - rev: v2.17.8 diff --git a/docs/__init__.py b/docs/__init__.py new file mode 100644 index 00000000..c01dd8cd --- /dev/null +++ b/docs/__init__.py @@ -0,0 +1,3 @@ +"""Documentation generation module.""" + +from __future__ import annotations diff --git a/docs/gen_api_nav.py b/docs/gen_api_nav.py index 7b89f535..aa4854a6 100644 --- a/docs/gen_api_nav.py +++ b/docs/gen_api_nav.py @@ -1,13 +1,16 @@ """Plugin for generate API docs.""" # Import built-in modules +from __future__ import annotations + from pathlib import Path # Import third-party modules import mkdocs_gen_files -def main(): +def main() -> None: + """Generate API navigation for mkdocs.""" nav = mkdocs_gen_files.Nav() root = Path(__file__).parent.parent api_root = root.joinpath("photoshop") @@ -16,11 +19,7 @@ def main(): doc_path = path.relative_to(root).with_suffix(".md") full_doc_path = Path("reference", doc_path) parts = list(module_path.parts) - if parts[-1] == "__init__": - continue - elif parts[-1] == "__main__": - continue - elif parts[-1] == "__version__": + if parts[-1] == "__init__" or parts[-1] == "__main__" or parts[-1] == "__version__": continue nav_parts = list(parts) if nav_parts[-1].startswith("_"): @@ -29,7 +28,7 @@ def main(): full_doc_path = full_doc_path.as_posix().replace("\\", "/") with mkdocs_gen_files.open(full_doc_path, "w") as fd: ident = ".".join(parts) - print(f"::: " + ident, file=fd) + print("::: " + ident, file=fd) mkdocs_gen_files.set_edit_path(full_doc_path, path.as_posix().replace("\\", "/")) diff --git a/docs/gen_examples.py b/docs/gen_examples.py index 04602901..b2bd6a13 100644 --- a/docs/gen_examples.py +++ b/docs/gen_examples.py @@ -1,14 +1,15 @@ """Plugin for generate API docs.""" # Import built-in modules -import os +from __future__ import annotations + from pathlib import Path +import re +import shutil # Import third-party modules -from jinja2 import Template import mkdocs_gen_files -import stringcase - +from jinja2 import Template template = Template( r""" @@ -22,44 +23,114 @@ ``` {% endfor %} -""" +""", ) -class Examples(object): - def __init__(self, root: Path): +class Examples: + """Class for handling example files generation.""" + + def __init__(self, root: Path) -> None: + """Initialize Examples class. + + Args: + root: Root directory path. + """ self._root = root - def get_examples(self): - files = [file_ for file_ in self._root.glob("*.py") if "_psd_files.py" not in file_.as_posix()] - return files + def get_examples(self) -> list[Path]: + """Get list of example files. + + Returns: + List of example file paths. + """ + return sorted([file_ for file_ in self._root.glob("*.py") if "_psd_files.py" not in file_.as_posix()]) @staticmethod - def convert_relative_path(file): - path = file.split("examples")[1] - return "../examples{}".format(path.replace("\\", "/")) + def convert_relative_path(file: str) -> str: + """Convert file path to relative path. + + Args: + file: File path to convert. + + Returns: + Relative path string. + """ + # Use raw string for regex pattern + return re.sub(r"\W+", "", str(file)) @staticmethod - def get_name(file): - name = os.path.basename(file).split(".py")[0] - return stringcase.titlecase(name) + def get_name(file: str) -> str: + """Get example name from file path. + + Args: + file: File path. + + Returns: + Example name in title case. + """ + name = Path(file).stem + return name.replace("_", " ").title() @staticmethod - def get_line(name): + def get_line(name: str) -> str: + """Generate underline for example name. + + Args: + name: Example name. + + Returns: + String of dashes matching name length. + """ return "-" * len(name) @staticmethod - def get_content(file_): - with open(file_, "r") as f: + def get_content(file_: str) -> str: + """Get content of example file. + + Args: + file_: File path. + + Returns: + File content as string. + """ + with Path(file_).open(encoding='utf-8') as f: return "".join(f.readlines()) -def main(): +def main() -> None: + """Generate examples documentation.""" root = Path(__file__).parent.parent - with mkdocs_gen_files.open("examples.md", "w") as nav_file: - examples_data = Examples(root.joinpath("examples")) - nav_file.write(template.render(Examples=examples_data)) - - -if __name__ == "": + examples_dir = root / "examples" + + # 生成 examples.md 文件 + examples_data = Examples(examples_dir) + content = template.render(Examples=examples_data) + + # 使用 mkdocs_gen_files 生成文件到正确的位置 + with mkdocs_gen_files.open("examples.md", "w", encoding='utf-8') as f: + f.write(content) + print("Generated examples.md") + + # 确保文件被写入到正确的位置 + mkdocs_gen_files.set_edit_path("examples.md", "docs/gen_examples.py") + + # 同时写入到 docs 目录 + docs_dir = root / "docs" + docs_dir.mkdir(exist_ok=True) + examples_file = docs_dir / "examples.md" + + # 如果文件已存在,先删除它 + if examples_file.exists(): + try: + examples_file.unlink() + except PermissionError: + pass + + # 写入新文件 + examples_file.write_text(content, encoding='utf-8') + print(f"Generated {examples_file}") + + +if __name__ == "__main__": main() diff --git a/docs/requirements.txt b/docs/requirements.txt index 4c4e111c..836dd96b 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,5 +1,4 @@ stringcase -mkdocs-material = "^8.2.5" mkdocstrings-python = "^0.6.6" mkdocs-pymdownx-material-extras = "^1.6" mkdocs-same-dir = "^0.1.1" @@ -9,3 +8,6 @@ mkdocs-autolinks-plugin = "^0.4.0" mkdocs-minify-plugin = "^0.5.0" mkdocs-git-revision-date-localized-plugin = "^1.0.0" mkdocs-literate-nav +mkdocs>=1.4.0 +mkdocs-material>=9.0.0 +mkdocs-material-extensions>=1.1 \ No newline at end of file diff --git a/examples/__init__.py b/examples/__init__.py new file mode 100644 index 00000000..9c6956df --- /dev/null +++ b/examples/__init__.py @@ -0,0 +1,3 @@ +"""Example scripts for photoshop-python-api.""" + +from __future__ import annotations diff --git a/examples/_psd_files.py b/examples/_psd_files.py index a7c2b6ad..099203e7 100644 --- a/examples/_psd_files.py +++ b/examples/_psd_files.py @@ -1,11 +1,14 @@ # Import built-in modules +from __future__ import annotations + import os +from pathlib import Path -def get_psd_files(): +def get_psd_files() -> dict[str, str]: files = {} - this_root = os.path.dirname(__file__) - file_root = os.path.join(this_root, "files") + this_root = Path(__file__).parent + file_root = this_root / "files" for file_name in os.listdir(file_root): - files[file_name] = os.path.join(file_root, file_name) + files[file_name] = str(file_root / file_name) return files diff --git a/examples/active_layer.py b/examples/active_layer.py index 1421281e..66d7b27b 100644 --- a/examples/active_layer.py +++ b/examples/active_layer.py @@ -1,20 +1,50 @@ -# Set the active layer to the last art layer of the active document, or the -# first if the last is already active. +"""Demonstrate how to work with active layers in Photoshop. + +This example shows how to: +1. Create a new document if none exists +2. Add new art layers to the document +3. Get and set the active layer +4. Rename layers + +Example: + ```python + with Session() as ps: + doc_ref = ps.app.documents.add() # Create new document + new_layer = doc_ref.artLayers.add() # Add new layer + new_layer.name = "test" # Rename layer + ``` + +Note: + The script will create a new document if none exists, + and will add a new layer if the document has less than MIN_LAYERS layers. +""" + +# Import built-in modules +from __future__ import annotations # Import local modules from photoshop import Session +def main(): + """Demonstrate active layer operations in Photoshop.""" + with Session() as ps: + # Create a new document if none exists + doc_ref = ps.app.documents.add() if len(ps.app.documents) < 1 else ps.app.activeDocument + + # Add a new layer if document has less than MIN_LAYERS layers + MIN_LAYERS = 2 + if len(doc_ref.layers) < MIN_LAYERS: + doc_ref.artLayers.add() + + # Print the name of the current active layer + ps.echo(doc_ref.activeLayer.name) -with Session() as ps: - if len(ps.app.documents) < 1: - docRef = ps.app.documents.add() - else: - docRef = ps.app.activeDocument + # Create a new art layer and make it active + new_layer = doc_ref.artLayers.add() + ps.echo(new_layer.name) - if len(docRef.layers) < 2: - docRef.artLayers.add() + # Rename the new layer + new_layer.name = "test" - ps.echo(docRef.activeLayer.name) - new_layer = docRef.artLayers.add() - ps.echo(new_layer.name) - new_layer.name = "test" +if __name__ == "__main__": + main() diff --git a/examples/add_metadata.py b/examples/add_metadata.py index 63d471bd..3c1ead6e 100644 --- a/examples/add_metadata.py +++ b/examples/add_metadata.py @@ -1,16 +1,40 @@ -"""Add metadata to current active document.""" +"""Demonstrate how to add metadata to a Photoshop document. + +This example shows how to add and modify metadata properties of the active document, +such as author, location, and title information. + +Example: + ```python + with Session(action="new_document") as ps: + doc = ps.active_document + doc.info.author = "John Doe" + doc.info.title = "My Project" + ``` + +Note: + - The script automatically creates a new document using the Session action parameter + - The author is set to the current system username by default + - You can view metadata in Photoshop via: File > File Info +""" # Import built-in modules +from __future__ import annotations + import os # Import local modules from photoshop import Session - +# Create a new Photoshop session and document with Session(action="new_document") as ps: + # Get reference to active document doc = ps.active_document - doc.info.author = os.getenv("USERNAME") - doc.info.provinceState = "Beijing" - doc.info.title = "My Demo" - print("Print all metadata of current active document.") + + # Set metadata properties + doc.info.author = os.getenv("USERNAME") # Set author to system username + doc.info.provinceState = "Beijing" # Set location information + doc.info.title = "My Demo" # Set document title + + # Print the metadata information + ps.echo("Metadata of current active document:") ps.echo(doc.info) diff --git a/examples/add_slate.py b/examples/add_slate.py index 85dc514e..53395d45 100644 --- a/examples/add_slate.py +++ b/examples/add_slate.py @@ -1,15 +1,36 @@ -"""Add slate information dynamically. +"""Demonstrate how to add and update slate information in a Photoshop template. -- Open template. -- Update info. -- Save as jpg. -- Close current document. +This example shows how to: +1. Open a PSD template file +2. Update text layers with dynamic information +3. Save the result as a JPG file +4. Display the saved file +The script uses a template PSD file that contains text layers within a layer set +named "template". It updates specific text layers with dynamic content like +project name and current date. + +Example: + ```python + with Session(template_file, action="open") as ps: + layer_set = ps.active_document.layerSets["template"] + for layer in layer_set.layers: + if layer.kind == ps.LayerKind.TextLayer: + layer.textItem.contents = dynamic_data[layer.textItem.contents] + ``` + +Note: + - Requires a PSD template file with text layers in a layer set named "template" + - Text layers should have placeholder text matching keys in the data dictionary + - The script uses os.startfile which is Windows-specific """ # Import built-in modules -from datetime import datetime +from __future__ import annotations + import os +from datetime import datetime, timezone +from pathlib import Path from tempfile import mkdtemp # Import third-party modules @@ -18,21 +39,31 @@ # Import local modules from photoshop import Session - +# Get path to template PSD file PSD_FILE = psd.get_psd_files() slate_template = PSD_FILE["slate_template.psd"] + +# Open template file in Photoshop with Session(slate_template, action="open", auto_close=True) as ps: - layer_set = ps.active_document.layerSets.getByName("template") + # Get the layer set named "template" + layer_set = ps.active_document.layerSets["template"] + # Prepare dynamic data for text layers data = { "project name": "test_project", - "datetime": datetime.today().strftime("%Y-%m-%d"), + "datetime": datetime.now(tz=timezone.utc).strftime("%Y-%m-%d"), } + + # Update text layers with dynamic content for layer in layer_set.layers: if layer.kind == ps.LayerKind.TextLayer: layer.textItem.contents = data[layer.textItem.contents.strip()] - jpg_file = os.path.join(mkdtemp("photoshop-python-api"), "slate.jpg") - ps.active_document.saveAs(jpg_file, ps.JPEGSaveOptions()) - print(f"Save jpg to {jpg_file}") - os.startfile(jpg_file) + # Save the document as JPG in a temporary directory + jpg_file = Path(mkdtemp("photoshop-python-api")) / "slate.jpg" + ps.active_document.saveAs(str(jpg_file), ps.JPEGSaveOptions()) + ps.echo(f"Save jpg to {jpg_file}") + + # Open the saved JPG file (Windows-specific) + # Note: os.startfile is Windows-specific, consider using a cross-platform solution + os.startfile(str(jpg_file)) diff --git a/examples/add_start_application_event.py b/examples/add_start_application_event.py index 3f28ec41..5913dde6 100644 --- a/examples/add_start_application_event.py +++ b/examples/add_start_application_event.py @@ -1,24 +1,46 @@ """Add event for Photoshop start application. -In the current example, every time we start photoshop it will -alert "Start Application Event". +This example demonstrates how to add an event listener that triggers when Photoshop starts. +Every time Photoshop is launched, it will display an alert message saying "Start Application Event". -Just like you manually in Script> Script Events Manager to enable the event. +This is equivalent to manually setting up an event in Photoshop through: +File > Scripts > Script Events Manager +Example: + ```python + with Session() as ps: + root = Path(mkdtemp()) + jsx_file = root / "event.jsx" + jsx_file.write_text('alert("Start Application event.")') + ps.app.notifiers.add(ps.EventID.Notify, str(jsx_file)) + ``` + +Note: + The event will persist even after the script finishes running. + To remove the event, use the Script Events Manager in Photoshop. """ # Import built-in modules -import os +from __future__ import annotations + +from pathlib import Path from tempfile import mkdtemp # Import local modules from photoshop import Session - +# Create a new Photoshop session with Session() as ps: - root = mkdtemp() - jsx_file = os.path.join(root, "event.jsx") - with open(jsx_file, "w") as f: - f.write('alert("Start Application event.")') - ps.app.notifiers.add(ps.EventID.Notify, jsx_file) - print("Add event done.") + # Create a temporary directory to store the JSX script + root = Path(mkdtemp()) + jsx_file = root / "event.jsx" + + # Write the JavaScript code that will be executed when Photoshop starts + jsx_file.write_text('alert("Start Application event.")') + + # Add the event notifier to Photoshop + # EventID.Notify is triggered when Photoshop starts + ps.app.notifiers.add(ps.EventID.Notify, str(jsx_file)) + + # Confirm the event was added successfully + ps.echo("Add event done.") diff --git a/examples/apply_crystallize_filter_action.py b/examples/apply_crystallize_filter_action.py index a22bf274..7ebfff68 100644 --- a/examples/apply_crystallize_filter_action.py +++ b/examples/apply_crystallize_filter_action.py @@ -1,40 +1,109 @@ -""" This script demonstrates how you can use the action manager +"""Demonstrate how to apply the Crystallize filter using Photoshop's native filter commands. -to execute the Crystallize filter. -In order to find all the IDs, see https://helpx.adobe.com/photoshop/kb/downloadable-plugins-and-content.html#ScriptingListenerplugin # noqa: E501 -This blog here explains what a script listener is http://blogs.adobe.com/crawlspace/2006/05/installing_and_1.html - -References: - https://github.com/lohriialo/photoshop-scripting-python/blob/master/ApplyCrystallizeFilterAction.py +This example shows how to: +1. Use Photoshop's native filter commands to apply filters +2. Work with layer sets and art layers +3. Apply the Crystallize filter +The script applies the Crystallize filter to the last art layer in the last layer set +of the document. """ # Import third-party modules -import examples._psd_files as psd # Import from examples. +from __future__ import annotations # Import local modules +import examples._psd_files as psd # Import from examples. from photoshop import Session +from photoshop.api.errors import PhotoshopPythonAPIError - +# Get sample PSD file path PSD_FILE = psd.get_psd_files() +# Open the PSD file in Photoshop with Session(PSD_FILE["layer_comps.psd"], "open") as ps: + app = ps.app active_document = ps.active_document - nLayerSets = active_document.layerSets - print(f"The total amount of current layerSet (Group) is " f"{len(nLayerSets)}.") - nArtLayers = active_document.layerSets.item(len(nLayerSets)).artLayers - # get the last layer in LayerSets - active_document.activeLayer = active_document.layerSets.item(len(nLayerSets)).artLayers.item(len(nArtLayers)) + # Get all layer sets (groups) in the document + layer_sets = active_document.layerSets + ps.echo(f"The total amount of current layerSet (Group) is {len(layer_sets)}.") + + # Get the last layer set + last_layer_set_index = len(layer_sets) - 1 + last_layer_set = layer_sets[last_layer_set_index] + art_layers = last_layer_set.artLayers + ps.echo(f"The total amount of art layers in the last layer set is {len(art_layers)}.") + + # Find a layer set with art layers + current_layer_set_index = last_layer_set_index + while len(art_layers) == 0 and current_layer_set_index > 0: + current_layer_set_index -= 1 + current_layer_set = layer_sets[current_layer_set_index] + art_layers = current_layer_set.artLayers + ps.echo(f"Checking layer set {current_layer_set.name}: {len(art_layers)} art layers") - def applyCrystallize(cellSize): - cellSizeID = ps.app.CharIDToTypeID("ClSz") - eventCrystallizeID = ps.app.CharIDToTypeID("Crst") + if len(art_layers) == 0: + raise PhotoshopPythonAPIError("No art layers found in any layer set") - filterDescriptor = ps.ActionDescriptor - filterDescriptor.putInteger(cellSizeID, cellSize) + # Set the last art layer in the current layer set as active + last_art_layer_index = len(art_layers) - 1 + last_art_layer = art_layers[last_art_layer_index] + active_document.activeLayer = last_art_layer + ps.echo(f"Selected layer: {last_art_layer.name}") - ps.app.executeAction(eventCrystallizeID, filterDescriptor) + def apply_crystallize(cell_size: int = 10) -> None: + """Apply the Crystallize filter to the active layer. + + Args: + cell_size: The size of the crystallize cells (1-300). + """ + # Apply the Crystallize filter using JavaScript + js_code = f""" + try {{ + // Get the active document and layer + var doc = app.activeDocument; + var layer = doc.activeLayer; + + // Create a reference to the current layer + var ref = new ActionReference(); + ref.putProperty(charIDToTypeID("Prpr"), charIDToTypeID("Lefx")); + ref.putEnumerated(charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt")); + + // Create a descriptor for the Crystallize filter + var desc = new ActionDescriptor(); + desc.putReference(charIDToTypeID("null"), ref); + + // Set the cell size + var filterDesc = new ActionDescriptor(); + filterDesc.putInteger(charIDToTypeID("ClSz"), {cell_size}); + + // Apply the filter + executeAction(charIDToTypeID("Crst"), filterDesc, DialogModes.NO); + }} catch (e) {{ + alert("Error: " + e); + }} + """ + app.doJavaScript(js_code) - applyCrystallize(25) - print("Apply crystallize done.") + # Apply the Crystallize filter with cell size 25 + try: + apply_crystallize(25) + ps.echo("Apply crystallize filter done.") + except Exception as e: + ps.echo(f"Error: {e!s}") + # Try alternative method using native filter + js_code = """ + try { + var desc = new ActionDescriptor(); + var ref = new ActionReference(); + ref.putClass(charIDToTypeID("Crst")); + desc.putReference(charIDToTypeID("null"), ref); + desc.putInteger(charIDToTypeID("ClSz"), 25); + executeAction(charIDToTypeID("Crst"), desc, DialogModes.NO); + } catch (e) { + alert("Error: " + e); + } + """ + app.doJavaScript(js_code) + ps.echo("Apply crystallize filter done (alternative method).") diff --git a/examples/apply_filters.py b/examples/apply_filters.py index bdaeedb3..75ec6184 100644 --- a/examples/apply_filters.py +++ b/examples/apply_filters.py @@ -1,61 +1,136 @@ -""" +"""Apply various filters to an image. + References: - https://github.com/lohriialo/photoshop-scripting-python/blob/master/ApplyFilters.py + https://github.com/lohriialo/photoshop-scripting-python/blob/master/ApplyFilters.py. +Example of applying various filters to a Photoshop document. """ + # Import third-party modules +from __future__ import annotations + import examples._psd_files as psd # Import from examples. # Import local modules -# selections in the open document. -import photoshop.api as ps - +from photoshop import Session +from photoshop.api.enumerations import SelectionType, NoiseDistribution PSD_FILE = psd.get_psd_files() -# Start up Photoshop application -app = ps.Application() - -# We don't want any Photoshop dialogs displayed during automated execution -app.displayDialogs = ps.DialogModes.DisplayNoDialogs - -psPixels = 1 -start_ruler_units = app.preferences.rulerUnits -if start_ruler_units is not psPixels: - app.preferences.rulerUnits = psPixels - -fileName = PSD_FILE["layer_comps.psd"] -docRef = app.open(fileName) -nLayerSets = len(list((i, x) for i, x in enumerate(docRef.layerSets))) - 1 -nArtLayers = len( - list((i, x) for i, x in enumerate(docRef.layerSets[nLayerSets].artLayers)), -) - -active_layer = docRef.activeLayer = docRef.layerSets[nLayerSets].artLayers[nArtLayers] -sel_area = ((0, 212), (300, 212), (300, 300), (0, 300)) -docRef.selection.select(sel_area, ps.SelectionType.ReplaceSelection, 20, True) -print(f"Current active layer: {active_layer.name}") -active_layer.applyAddNoise(15, ps.NoiseDistribution.GaussianNoise, False) - -backColor = ps.SolidColor() -backColor.hsb.hue = 0 -backColor.hsb.saturation = 0 -backColor.hsb.brightness = 100 -app.backgroundColor = backColor - -sel_area2 = ((120, 20), (210, 20), (210, 110), (120, 110)) -docRef.selection.select(sel_area2, ps.SelectionType.ReplaceSelection, 25, False) -active_layer.applyDiffuseGlow(9, 12, 15) -active_layer.applyGlassEffect( - 7, - 3, - 7, - False, - ps.TextureType.TinyLensTexture, - None, -) -docRef.selection.deselect() - -# Set ruler units back the way we found it. -if start_ruler_units is not psPixels: - app.Preferences.RulerUnits = start_ruler_units +with Session() as ps: + app = ps.app + # We don't want any Photoshop dialogs displayed during automated execution + app.displayDialogs = ps.DialogModes.DisplayNoDialogs + + PS_PIXELS = 1 + start_ruler_units = app.preferences.rulerUnits + if start_ruler_units is not PS_PIXELS: + app.preferences.rulerUnits = PS_PIXELS + + file_name = PSD_FILE["layer_comps.psd"] + doc_ref = app.open(file_name) + + # Get the first layer set that has art layers + layer_sets = doc_ref.layerSets + target_layer_set = None + target_art_layer = None + + for layer_set in layer_sets: + if len(layer_set.artLayers) > 0: + target_layer_set = layer_set + target_art_layer = layer_set.artLayers[0] # Get the first art layer + break + + if target_art_layer is None: + raise Exception("No art layers found in any layer set") + + # Set the active layer + doc_ref.activeLayer = target_art_layer + ps.echo(f"Current active layer: {target_art_layer.name}") + + # Create different selection areas for different effects + sel_area1 = ((0, 0), (100, 0), (100, 100), (0, 100)) + sel_area2 = ((120, 20), (210, 20), (210, 110), (120, 110)) + sel_area3 = ((220, 20), (310, 20), (310, 110), (220, 110)) + sel_area4 = ((0, 120), (100, 120), (100, 210), (0, 210)) + + try: + # Apply Gaussian Blur to first area + doc_ref.selection.select(sel_area1, SelectionType.ReplaceSelection, 0, False) + target_art_layer.applyGaussianBlur(2.5) + ps.echo("Applied Gaussian Blur") + + # Apply Add Noise to second area + doc_ref.selection.select(sel_area2, SelectionType.ReplaceSelection, 0, False) + target_art_layer.applyAddNoise(15.0, NoiseDistribution.GaussianNoise, True) + ps.echo("Applied Add Noise") + + # Apply Diffuse Glow to third area + doc_ref.selection.select(sel_area3, SelectionType.ReplaceSelection, 0, False) + target_art_layer.applyDiffuseGlow(9, 12, 15) + ps.echo("Applied Diffuse Glow") + + # Apply Glass Effect to fourth area + doc_ref.selection.select(sel_area4, SelectionType.ReplaceSelection, 0, False) + # Apply Glass Effect using JavaScript + js_code = """ + try { + var desc = new ActionDescriptor(); + desc.putInteger(charIDToTypeID("Dstn"), 7); // Distortion + desc.putInteger(charIDToTypeID("Smth"), 3); // Smoothness + desc.putInteger(charIDToTypeID("Scl "), 7); // Scaling + desc.putBoolean(charIDToTypeID("Invr"), false); // Invert + desc.putInteger(charIDToTypeID("TxtT"), 1); // Texture Type + executeAction(charIDToTypeID("GlsE"), desc, DialogModes.NO); + } catch (e) { + alert("Error: " + e); + } + """ + app.doJavaScript(js_code) + ps.echo("Applied Glass Effect") + + # Apply Crystallize filter to a new area + sel_area5 = ((120, 120), (210, 120), (210, 210), (120, 210)) + doc_ref.selection.select(sel_area5, SelectionType.ReplaceSelection, 0, False) + + # Apply Crystallize filter using JavaScript + js_code = """ + try { + var desc = new ActionDescriptor(); + desc.putInteger(charIDToTypeID("ClSz"), 15); // Cell size 15 + executeAction(charIDToTypeID("Crst"), desc, DialogModes.NO); + } catch (e) { + alert("Error: " + e); + } + """ + app.doJavaScript(js_code) + ps.echo("Applied Crystallize filter") + + # Apply Motion Blur to a new area + sel_area6 = ((220, 120), (310, 120), (310, 210), (220, 210)) + doc_ref.selection.select(sel_area6, SelectionType.ReplaceSelection, 0, False) + + # Apply Motion Blur using JavaScript + js_code = """ + try { + var desc = new ActionDescriptor(); + desc.putInteger(charIDToTypeID("Angl"), 45); // Angle + desc.putInteger(charIDToTypeID("Dst "), 20); // Distance + executeAction(charIDToTypeID("MtnB"), desc, DialogModes.NO); + } catch (e) { + alert("Error: " + e); + } + """ + app.doJavaScript(js_code) + ps.echo("Applied Motion Blur") + + except Exception as e: + ps.echo(f"Error: {e!s}") + finally: + # Deselect all + doc_ref.selection.deselect() + ps.echo("All filters applied successfully.") + + # Set ruler units back the way we found it + if start_ruler_units is not PS_PIXELS: + app.preferences.rulerUnits = start_ruler_units diff --git a/examples/change_color_of_background_and_foreground.py b/examples/change_color_of_background_and_foreground.py index c4ae2faf..5f3a63dc 100644 --- a/examples/change_color_of_background_and_foreground.py +++ b/examples/change_color_of_background_and_foreground.py @@ -1,17 +1,73 @@ -"""Change the color of the background and foreground.""" +"""Change the color of the background and foreground. + +This example demonstrates how to: +1. Create SolidColor objects for foreground and background colors +2. Set RGB values for each color +3. Apply these colors to Photoshop's foreground and background +4. Print the current color values for verification +""" + # Import local modules +from __future__ import annotations + from photoshop import Session +from photoshop.api.enumerations import NewDocumentMode, DocumentFill +def rgb_to_hex(r: int, g: int, b: int) -> str: + """Convert RGB values to hex color code. + + Args: + r: Red value (0-255) + g: Green value (0-255) + b: Blue value (0-255) + + Returns: + Hex color code (e.g. '#FF0000' for red) + """ + return f"#{r:02x}{g:02x}{b:02x}".upper() + +def print_color_info(name: str, color) -> None: + """Print color information in both RGB and HEX formats. + + Args: + name: Name of the color (e.g. 'Foreground' or 'Background') + color: SolidColor object containing RGB values + """ + print(f"{name} Color:") + print(f" RGB: ({color.rgb.red}, {color.rgb.green}, {color.rgb.blue})") + print(f" HEX: {rgb_to_hex(color.rgb.red, color.rgb.green, color.rgb.blue)}") with Session() as ps: + # Create a new document to work with + doc = ps.app.documents.add( + width=800, + height=600, + resolution=72, + name="Color Test", + mode=NewDocumentMode.NewRGB, + initialFill=DocumentFill.White, + ) + + # Set foreground color to red foregroundColor = ps.SolidColor() - foregroundColor.rgb.red = 255 - foregroundColor.rgb.green = 0 - foregroundColor.rgb.blue = 0 + foregroundColor.rgb.red = 255 # Max red + foregroundColor.rgb.green = 0 # No green + foregroundColor.rgb.blue = 0 # No blue ps.app.foregroundColor = foregroundColor - + print_color_info("Foreground", foregroundColor) + + # Set background color to black backgroundColor = ps.SolidColor() - backgroundColor.rgb.red = 0 - backgroundColor.rgb.green = 0 - backgroundColor.rgb.blue = 0 + backgroundColor.rgb.red = 0 # No red + backgroundColor.rgb.green = 0 # No green + backgroundColor.rgb.blue = 0 # No blue ps.app.backgroundColor = backgroundColor + print_color_info("Background", backgroundColor) + + # Fill a selection with the foreground color to demonstrate the change + doc.selection.selectAll() + doc.selection.fill(foregroundColor) + doc.selection.deselect() + + print("\nColors have been successfully updated in Photoshop!") + print("A new document has been created and filled with the foreground color to demonstrate the change.") diff --git a/examples/color.py b/examples/color.py index bbb0475a..99422a6c 100644 --- a/examples/color.py +++ b/examples/color.py @@ -1,25 +1,63 @@ -# Import local modules +"""Example of working with colors in Photoshop. + +This script demonstrates how to: +1. Create a solid color +2. Set RGB values +3. Apply color to text layers +4. Verify color values + +Module Attributes: + None + +Module Classes: + None + +Module Functions: + main() -> None +""" + + +# Import local modules`` +from __future__ import annotations from photoshop import Session +def main() -> None: + """Run the example script. + + This function creates a solid color, sets RGB values, applies the color to a text layer, + and verifies the color values. + + Returns: + None + """ + with Session() as ps: + doc = ps.active_document + # Add a solid color. + text_color = ps.SolidColor() + text_color.rgb.red = 255.0 + text_color.rgb.green = 197 + text_color.rgb.blue = 0 + + # Create empty layer. + new_text_layer = doc.artLayers.add() + # Set empty layer type to text layer + new_text_layer.kind = ps.LayerKind.TextLayer + # Set current text layer contents to "Hello, World!". + new_text_layer.textItem.contents = "Hello, World!" + # Set font to Microsoft YaHei + new_text_layer.textItem.font = "Microsoft YaHei" + # Change current text layer position. + new_text_layer.textItem.position = [160, 167] + # Change current text layer text size. + new_text_layer.textItem.size = 36 + # Change current text layer color. + new_text_layer.textItem.color = text_color + + # Verify the color values + if new_text_layer.textItem.color.rgb.red == text_color.rgb.red: + ps.echo("Text color set successfully") + else: + ps.echo("Failed to set text color") -with Session() as ps: - doc = ps.active_document - # Add a solid color. - textColor = ps.SolidColor() - textColor.rgb.red = 255.0 - textColor.rgb.green = 197 - textColor.rgb.blue = 255 - - # Create empty layer. - new_text_layer = doc.artLayers.add() - # Set empty layer type to text layer - new_text_layer.kind = ps.LayerKind.TextLayer - # Set current text layer contents to "Hello, World!". - new_text_layer.textItem.contents = "Hello, World!" - # Change current text layer position. - new_text_layer.textItem.position = [160, 167] - # Change current text layer text size. - new_text_layer.textItem.size = 36 - # Change current text layer color. - new_text_layer.textItem.color = textColor - assert new_text_layer.textItem.color.rgb.red == textColor.rgb.red +if __name__ == "__main__": + main() diff --git a/examples/compare_colors.py b/examples/compare_colors.py index 9bc41d83..a4b002c1 100644 --- a/examples/compare_colors.py +++ b/examples/compare_colors.py @@ -1,15 +1,196 @@ -"""Check whether the foreground is equal to the background color. +"""Compare colors in Photoshop. + +This example demonstrates how to: +1. Get and set foreground and background colors +2. Compare colors using isEqual method +3. Print color information in RGB and HEX formats +4. Create a color palette demonstration +5. Work with different color models (RGB, HSB) References: https://github.com/lohriialo/photoshop-scripting-python/blob/master/CompareColors.py - """ + # Import local modules +from __future__ import annotations +from typing import Tuple, Dict +import math + from photoshop import Session +from photoshop.api.enumerations import NewDocumentMode, DocumentFill, LayerKind + +# Define some common colors with their names +COLORS = { + "Red": (255, 0, 0), + "Green": (0, 255, 0), + "Blue": (0, 0, 255), + "Yellow": (255, 255, 0), + "Magenta": (255, 0, 255), + "Cyan": (0, 255, 255), + "Orange": (255, 165, 0), + "Purple": (128, 0, 128), + "Pink": (255, 192, 203), + "Turquoise": (64, 224, 208), +} + +def rgb_to_hex(r: int, g: int, b: int) -> str: + """Convert RGB values to hex color code. + + Args: + r: Red value (0-255) + g: Green value (0-255) + b: Blue value (0-255) + + Returns: + Hex color code (e.g. '#FF0000' for red) + """ + return f"#{r:02x}{g:02x}{b:02x}".upper() +def rgb_to_hsb(r: int, g: int, b: int) -> Tuple[float, float, float]: + """Convert RGB to HSB/HSV. + + Args: + r: Red value (0-255) + g: Green value (0-255) + b: Blue value (0-255) + + Returns: + Tuple of (Hue, Saturation, Brightness) + """ + r, g, b = r / 255.0, g / 255.0, b / 255.0 + cmax = max(r, g, b) + cmin = min(r, g, b) + diff = cmax - cmin -with Session() as ps: - if ps.app.foregroundColor.isEqual(ps.app.backgroundColor): - ps.echo("They're Equal.") + if cmax == cmin: + h = 0 + elif cmax == r: + h = (60 * ((g - b) / diff) + 360) % 360 + elif cmax == g: + h = (60 * ((b - r) / diff) + 120) % 360 else: - ps.echo("NOT Equal.") + h = (60 * ((r - g) / diff) + 240) % 360 + + s = 0 if cmax == 0 else (diff / cmax) * 100 + v = cmax * 100 + return h, s, v + +def print_color_info(name: str, color) -> None: + """Print color information in RGB, HEX and HSB formats. + + Args: + name: Name of the color + color: SolidColor object containing RGB values + """ + rgb = (color.rgb.red, color.rgb.green, color.rgb.blue) + hsb = rgb_to_hsb(*rgb) + print(f"{name} Color:") + print(f" RGB: {rgb}") + print(f" HEX: {rgb_to_hex(*rgb)}") + print(f" HSB: H:{hsb[0]:.1f}° S:{hsb[1]:.1f}% B:{hsb[2]:.1f}%") + +def create_color_grid(doc, colors: Dict[str, Tuple[int, int, int]], ps) -> None: + """Create a grid of color squares with labels. + + Args: + doc: Photoshop document + colors: Dictionary of color names and their RGB values + ps: Photoshop session + """ + num_colors = len(colors) + cols = 5 # Number of columns in the grid + rows = math.ceil(num_colors / cols) + + square_size = 100 + margin = 20 + text_height = 30 + + # Calculate total dimensions + total_width = cols * (square_size + margin) - margin + total_height = rows * (square_size + text_height + margin) - margin + + # Resize document to fit the grid + doc.resizeImage(width=total_width, height=total_height) + + for i, (name, (r, g, b)) in enumerate(colors.items()): + row = i // cols + col = i % cols + + # Calculate position + x = col * (square_size + margin) + y = row * (square_size + text_height + margin) + + # Create color square layer + square_layer = doc.artLayers.add() + square_layer.name = f"{name} Square" + + # Fill with color + color = ps.SolidColor() + color.rgb.red = r + color.rgb.green = g + color.rgb.blue = b + + # Select area for color square + doc.selection.select([ + [x, y], + [x + square_size, y], + [x + square_size, y + square_size], + [x, y + square_size], + ]) + + # Set foreground color and fill + ps.app.foregroundColor = color + doc.selection.fill(ps.app.foregroundColor) + doc.selection.deselect() + + # Add text label + text_layer = doc.artLayers.add() + text_layer.kind = LayerKind.TextLayer + text_layer.name = f"{name} Label" + text_layer.textItem.contents = f"{name}\n{rgb_to_hex(r, g, b)}" + text_layer.textItem.position = [x, y + square_size + 5] + text_layer.textItem.size = 12 + text_layer.textItem.font = "Microsoft YaHei" + + # Set text color (black or white depending on background) + text_color = ps.SolidColor() + brightness = (r * 299 + g * 587 + b * 114) / 1000 + if brightness < 128: + text_color.rgb.red = text_color.rgb.green = text_color.rgb.blue = 255 + else: + text_color.rgb.red = text_color.rgb.green = text_color.rgb.blue = 0 + text_layer.textItem.color = text_color + +def main() -> None: + """Run the example script.""" + with Session() as ps: + # Create a new document + doc = ps.app.documents.add( + width=800, + height=600, + resolution=72, + name="Color Palette Demo", + mode=NewDocumentMode.NewRGB, + initialFill=DocumentFill.White, + ) + + # Create color grid + create_color_grid(doc, COLORS, ps) + + # Print color information + print("Color Palette Information:") + print("-" * 50) + for name, (r, g, b) in COLORS.items(): + color = ps.SolidColor() + color.rgb.red = r + color.rgb.green = g + color.rgb.blue = b + print_color_info(name, color) + print() + + print("\nA color palette has been created in Photoshop!") + print(f"The palette contains {len(COLORS)} different colors with their RGB, HEX, and HSB values.") + print("Each color is displayed as a square with its name and HEX code.") + +if __name__ == "__main__": + main() diff --git a/examples/convert_smartobject_to_layer.py b/examples/convert_smartobject_to_layer.py index a0022d9a..64fd5f4f 100644 --- a/examples/convert_smartobject_to_layer.py +++ b/examples/convert_smartobject_to_layer.py @@ -1,19 +1,157 @@ -"""Convert Smart object to artLayer.""" +"""Example of converting a smart object to a regular layer in Photoshop. + +This script demonstrates how to: +1. Create a smart object from a regular layer +2. Convert a smart object back to a regular layer +3. Handle multiple selected layers +4. Provide visual feedback about the conversion process + +Module Attributes: + None + +Module Classes: + None + +Module Functions: + create_sample_document() -> None + convert_to_smart_object() -> None + convert_to_layer() -> None + main() -> None +""" # Import local modules +from __future__ import annotations +import time + from photoshop import Session +from photoshop.api.enumerations import NewDocumentMode, DocumentFill, LayerKind +def create_sample_document(ps) -> None: + """Create a sample document with multiple layers. + + Args: + ps: Photoshop session object + """ + # Create a new document + doc = ps.app.documents.add( + width=500, + height=300, + resolution=72, + name="Smart Object Demo", + mode=NewDocumentMode.NewRGB, + initialFill=DocumentFill.White, + ) + + # Create some sample layers + colors = [ + ("Red Layer", (255, 0, 0)), + ("Green Layer", (0, 255, 0)), + ("Blue Layer", (0, 0, 255)), + ] + + for name, (r, g, b) in colors: + # Create a new layer + layer = doc.artLayers.add() + layer.name = name + + # Set layer fill color + color = ps.SolidColor() + color.rgb.red = r + color.rgb.green = g + color.rgb.blue = b + + # Create a rectangle selection + doc.selection.select([ + [100, 100], + [400, 100], + [400, 200], + [100, 200], + ]) + + # Fill the selection with color + ps.app.foregroundColor = color + doc.selection.fill(ps.app.foregroundColor) + doc.selection.deselect() + + print("Created sample document with three colored layers") -# example 1 -with Session() as ps: +def convert_to_smart_object(ps) -> bool: + """Convert selected layers to a smart object using JavaScript. + + Args: + ps: Photoshop session object + + Returns: + bool: True if conversion was successful, False otherwise + """ js = """ -var idplacedLayerConvertToLayers = stringIDToTypeID( "placedLayerConvertToLayers" ); -executeAction( idplacedLayerConvertToLayers, undefined, DialogModes.NO ); -""" - ps.app.doJavaScript(js) + try { + var idnewPlacedLayer = stringIDToTypeID("newPlacedLayer"); + executeAction(idnewPlacedLayer, undefined, DialogModes.NO); + "success" + } catch(e) { + e.message + } + """ + result = ps.app.doJavaScript(js) + success = result == "success" + print(f"Smart Object conversion: {'Successful' if success else 'Failed'}") + return success + +def convert_to_layer(ps) -> None: + """Convert smart object back to regular layer using JavaScript. + + Args: + ps: Photoshop session object + """ + js = """ + try { + var idplacedLayerConvertToLayers = stringIDToTypeID("placedLayerConvertToLayers"); + executeAction(idplacedLayerConvertToLayers, undefined, DialogModes.NO); + "success" + } catch(e) { + e.message + } + """ + result = ps.app.doJavaScript(js) + print(f"Regular Layer conversion: {'Successful' if result == 'success' else 'Failed - ' + str(result)}") + +def main() -> None: + """Run the example script. + + This function demonstrates the complete workflow: + 1. Create a sample document with multiple layers + 2. Convert layers to smart object + 3. Convert smart object back to regular layer + + Returns: + None + """ + with Session() as ps: + # Step 1: Create sample document + create_sample_document(ps) + + # Step 2: Select all layers except background + doc = ps.active_document + for layer in doc.artLayers: + if layer.name != "Background": + layer.selected = True + print("\nSelected all non-background layers") + + # Step 3: Convert to Smart Object + print("\nConverting to Smart Object...") + if convert_to_smart_object(ps): + # Wait a moment to ensure Photoshop has processed the smart object + time.sleep(0.5) + + # Step 4: Convert back to regular layer + print("\nConverting back to regular layer...") + convert_to_layer(ps) + + print("\nProcess completed! Check the layers panel in Photoshop.") + print("The document contains the following layers:") + for i, layer in enumerate(doc.artLayers, 1): + print(f"{i}. {layer.name} ({LayerKind(layer.kind).name})") -# example 2 -with Session() as ps: - descriptor = ps.ActionDescriptor - idplacedLayerConvertToLayers = ps.app.stringIDToTypeID("placedLayerConvertToLayers") - ps.app.executeAction(idplacedLayerConvertToLayers, descriptor) +if __name__ == "__main__": + main() diff --git a/examples/copy_and_paste.py b/examples/copy_and_paste.py index 3db42a1f..baf661bd 100644 --- a/examples/copy_and_paste.py +++ b/examples/copy_and_paste.py @@ -1,43 +1,198 @@ -""" -References: - https://github.com/lohriialo/photoshop-scripting-python/blob/master/CopyAndPaste.py - -""" +"""Example of copying and pasting in Photoshop. -# Import local modules -import photoshop.api as ps +This script demonstrates how to: +1. Create a source document with sample content +2. Select different areas (full document, partial area) +3. Copy the selection with various options +4. Create a new document with matching properties +5. Paste the copied content with proper positioning +6. Handle different layer types (normal, text, smart object) +Module Attributes: + None -app = ps.Application() +Module Classes: + None -startRulerUnits = app.preferences.rulerUnits - -app.preferences.rulerUnits = ps.Units.Inches +Module Functions: + create_sample_document() -> None + copy_paste_area() -> None + main() -> None +""" -doc = app.documents.add(7, 5, 72, None, ps.NewDocumentMode.NewRGB, ps.DocumentFill.White) +from __future__ import annotations -# Make sure the active layer is not a text layer, which cannot be copied to the -# clipboard. -if doc.activeLayer.kind != ps.LayerKind.TextLayer: - # Select the left half of the document. Selections are always expressed - # in pixels regardless of the current ruler unit type, so we're computing - # the selection corner points based on the inch unit width and height - # of the document - x2 = (doc.width * doc.resolution) / 2 - y2 = doc.height * doc.resolution +# Import local modules +from photoshop import Session +from photoshop.api import Units +from photoshop.api.enumerations import NewDocumentMode, DocumentFill, LayerKind - sel_area = ((0, 0), (x2, 0), (x2, y2), (0, y2)) - doc.selection.select(sel_area, ps.SelectionType.ReplaceSelection, 0, False) +def create_sample_document(ps, name: str = "Source Document") -> None: + """Create a sample document with various content. + + Args: + ps: Photoshop session object + name: Name of the document to create + """ + # Create a new document + doc = ps.app.documents.add( + width=500, + height=300, + resolution=72, + name=name, + mode=NewDocumentMode.NewRGB, + initialFill=DocumentFill.White, + ) + + # Create a shape layer + shape_layer = doc.artLayers.add() + shape_layer.name = "Blue Rectangle" + + # Set shape color + color = ps.SolidColor() + color.rgb.red = 0 + color.rgb.green = 0 + color.rgb.blue = 255 + + # Create shape selection + doc.selection.select([ + [50, 50], + [450, 50], + [450, 250], + [50, 250], + ]) + + # Fill shape + ps.app.foregroundColor = color + doc.selection.fill(ps.app.foregroundColor) + doc.selection.deselect() + + # Add text layer + text_layer = doc.artLayers.add() + text_layer.kind = LayerKind.TextLayer + text_layer.name = "Sample Text" + text_layer.textItem.contents = "Copy & Paste Demo" + text_layer.textItem.position = [150, 150] + text_layer.textItem.size = 24 + text_layer.textItem.font = "Microsoft YaHei" + + print(f"Created sample document: {name}") - doc.selection.copy() +def copy_paste_area( + ps, + source_doc, + paste_doc_name: str = "Paste Target", + method: str = "all", +) -> None: + """Copy an area from source document and paste into a new document. + + Args: + ps: Photoshop session object + source_doc: Source document to copy from + paste_doc_name: Name for the new document + method: Copy method ('all', 'merged', 'layer') + """ + # Get document dimensions + doc_width = int(source_doc.width) + doc_height = int(source_doc.height) + + # Select appropriate copy method + js = "" + if method == "all": + js = """ + try { + app.activeDocument.selection.selectAll(); + app.activeDocument.selection.copy(); + app.activeDocument.selection.deselect(); + "success" + } catch(e) { + e.message + } + """ + elif method == "merged": + js = """ + try { + app.activeDocument.selection.selectAll(); + app.activeDocument.selection.copy(true); + app.activeDocument.selection.deselect(); + "success" + } catch(e) { + e.message + } + """ + elif method == "layer": + js = """ + try { + app.activeDocument.activeLayer.copy(); + "success" + } catch(e) { + e.message + } + """ + + # Execute copy operation + result = ps.app.doJavaScript(js) + if result != "success": + print(f"Warning: Copy operation failed - {result}") + return + + # Create and activate new document + paste_doc = ps.app.documents.add( + width=doc_width, + height=doc_height, + resolution=source_doc.resolution, + name=paste_doc_name, + mode=source_doc.mode, + initialFill=DocumentFill.Transparent, + ) + + # Paste and position content + paste_doc.paste() + print(f"Content copied to new document: {paste_doc_name}") - # The new doc is created - # need to change ruler units to pixels because x2 and y2 are pixel units. - app.preferences.rulerUnits = ps.Units.Pixels - pasteDoc = app.documents.add(x2, y2, doc.resolution, "Paste Target") - pasteDoc.paste() -else: - print("You cannot copy from a text layer") +def main() -> None: + """Run the example script. + + This function demonstrates different copy and paste scenarios: + 1. Copy entire document + 2. Copy merged layers + 3. Copy active layer + + Returns: + None + """ + with Session() as ps: + # Store original ruler units + app = ps.app + start_ruler_units = app.preferences.rulerUnits + app.preferences.rulerUnits = Units.Pixels + + try: + # Create source document + create_sample_document(ps, "Source Document") + source_doc = app.activeDocument + + # Example 1: Copy entire document + print("\nExample 1: Copying entire document...") + copy_paste_area(ps, source_doc, paste_doc_name="Full Copy", method="all") + + # Example 2: Copy merged layers + print("\nExample 2: Copying merged layers...") + copy_paste_area(ps, source_doc, paste_doc_name="Merged Copy", method="merged") + + # Example 3: Copy active layer + print("\nExample 3: Copying active layer...") + copy_paste_area(ps, source_doc, paste_doc_name="Layer Copy", method="layer") + + print("\nProcess completed! Check the following documents in Photoshop:") + print("1. Source Document (original content)") + print("2. Full Copy (all layers copied)") + print("3. Merged Copy (flattened copy)") + print("4. Layer Copy (active layer only)") + + finally: + # Restore ruler units + app.preferences.rulerUnits = start_ruler_units -if startRulerUnits != app.preferences.rulerUnits: - app.preferences.rulerUnits = startRulerUnits +if __name__ == "__main__": + main() diff --git a/examples/create_new_document.py b/examples/create_new_document.py index 3f1cd953..64f810f3 100644 --- a/examples/create_new_document.py +++ b/examples/create_new_document.py @@ -1,9 +1,185 @@ -"""Create a new document.""" +"""Example of creating new documents in Photoshop. + +This script demonstrates how to: +1. Create documents with different sizes and settings +2. Use various color modes and resolutions +3. Set initial fill and background colors +4. Handle different measurement units +5. Create documents from presets + +Module Attributes: + None + +Module Classes: + None + +Module Functions: + create_basic_document() -> None + create_custom_document() -> None + create_preset_document() -> None + main() -> None +""" + +from __future__ import annotations +from typing import Optional # Import local modules from photoshop import Session +from photoshop.api import Units +from photoshop.api.enumerations import ( + NewDocumentMode, + DocumentFill, +) + +def create_basic_document( + ps, + name: str, + width: int, + height: int, + resolution: float = 72.0, + mode: NewDocumentMode = NewDocumentMode.NewRGB, +) -> None: + """Create a basic document with minimal settings. + + Args: + ps: Photoshop session object + name: Name of the document + width: Width in pixels + height: Height in pixels + resolution: Resolution in pixels/inch (default: 72.0) + mode: Color mode (default: RGB) + """ + doc = ps.app.documents.add( + width=width, + height=height, + resolution=resolution, + name=name, + mode=mode, + initialFill=DocumentFill.White, + ) + print(f"Created basic document: {name}") + print(f" Size: {width}x{height} pixels") + print(f" Resolution: {resolution} ppi") + print(f" Mode: {mode.name}") + +def create_custom_document( + ps, + name: str, + width: int, + height: int, + resolution: float = 300.0, + mode: NewDocumentMode = NewDocumentMode.NewRGB, + bits_per_channel: int = 8, + fill: DocumentFill = DocumentFill.Transparent, + color_profile: Optional[str] = None, +) -> None: + """Create a document with custom settings. + + Args: + ps: Photoshop session object + name: Name of the document + width: Width in pixels + height: Height in pixels + resolution: Resolution in pixels/inch (default: 300.0) + mode: Color mode (default: RGB) + bits_per_channel: Bit depth (default: 8) + fill: Initial fill type (default: Transparent) + color_profile: Color profile name (default: None) + """ + doc = ps.app.documents.add( + width=width, + height=height, + resolution=resolution, + name=name, + mode=mode, + initialFill=fill, + bitsPerChannel=bits_per_channel, + colorProfileName=color_profile, + ) + print(f"\nCreated custom document: {name}") + print(f" Size: {width}x{height} pixels") + print(f" Resolution: {resolution} ppi") + print(f" Mode: {mode.name}") + print(f" Bit Depth: {bits_per_channel} bits/channel") + print(f" Fill: {fill.name}") + if color_profile: + print(f" Color Profile: {color_profile}") + +def create_preset_document(ps, preset_name: str) -> None: + """Create a document from a preset using JavaScript. + + Args: + ps: Photoshop session object + preset_name: Name of the preset to use + """ + js = f""" + try {{ + var idMk = charIDToTypeID("Mk "); + var desc = new ActionDescriptor(); + var idNw = charIDToTypeID("Nw "); + desc.putString(idNw, "{preset_name}"); + executeAction(idMk, desc, DialogModes.NO); + "success" + }} catch(e) {{ + e.message + }} + """ + result = ps.app.doJavaScript(js) + if result == "success": + print(f"\nCreated document from preset: {preset_name}") + else: + print(f"\nFailed to create document from preset: {result}") +def main() -> None: + """Run the example script. + + This function demonstrates different document creation scenarios: + 1. Basic document (HD video size) + 2. Custom document (print-ready) + 3. Preset document (common sizes) + + Returns: + None + """ + with Session() as ps: + # Store original ruler units + app = ps.app + start_ruler_units = app.preferences.rulerUnits + app.preferences.rulerUnits = Units.Pixels + + try: + # Example 1: Create a basic HD document + create_basic_document( + ps, + name="HD Video", + width=1920, + height=1080, + ) + + # Example 2: Create a custom print document + create_custom_document( + ps, + name="Print Ready", + width=3508, # A4 size at 300ppi + height=4961, + resolution=300.0, + mode=NewDocumentMode.NewCMYK, + bits_per_channel=16, + fill=DocumentFill.White, + color_profile="U.S. Web Coated (SWOP) v2", + ) + + # Example 3: Create from preset + create_preset_document(ps, "Web") + + print("\nProcess completed! Created the following documents:") + print("1. HD Video (basic HD 1080p document)") + print("2. Print Ready (custom CMYK print document)") + print("3. Web (from preset)") + + finally: + # Restore ruler units + app.preferences.rulerUnits = start_ruler_units -with Session() as ps: - ps.app.preferences.rulerUnits = ps.Units.Pixels - ps.app.documents.add(1920, 1080, name="my_new_document") +if __name__ == "__main__": + main() diff --git a/examples/create_thumbnail.py b/examples/create_thumbnail.py index 1897e61b..e8a5d108 100644 --- a/examples/create_thumbnail.py +++ b/examples/create_thumbnail.py @@ -1,49 +1,400 @@ -"""Create a thumbnail image for currently active document. +"""Example of creating thumbnail images in Photoshop. -You can use the thumbnail image to upload to Shotgun or Ftrack. +This script demonstrates how to: +1. Create thumbnails from the active document +2. Support different output formats (JPEG, PNG) +3. Maintain aspect ratio while resizing +4. Handle different quality settings +5. Support multiple output directories +Module Attributes: + DEFAULT_QUALITY: Default JPEG quality setting (0-12) + DEFAULT_MAX_RESOLUTION: Default maximum resolution for thumbnails + SUPPORTED_FORMATS: List of supported output formats + +Module Classes: + None + +Module Functions: + create_sample_document() -> None + create_thumbnail() -> str + create_multiple_thumbnails() -> list[str] + main() -> None """ # Import built-in modules +from __future__ import annotations + import os +import time +from pathlib import Path from tempfile import mkdtemp +from typing import Optional, Literal # Import local modules from photoshop import Session +from photoshop.api import Units +from photoshop.api.enumerations import ( + NewDocumentMode, + DocumentFill, + LayerKind, +) +# Constants +DEFAULT_QUALITY = 10 +DEFAULT_MAX_RESOLUTION = 512 +SUPPORTED_FORMATS = Literal["jpg", "png"] -def create_thumbnail(output_path=None, max_resolution=512): +def create_sample_document(ps, name: str = "Sample Document") -> None: + """Create a sample document with various content. + + Args: + ps: Photoshop session object + name: Name of the document to create + """ + # Create a new document + doc = ps.app.documents.add( + width=800, + height=600, + resolution=72, + name=name, + mode=NewDocumentMode.NewRGB, + initialFill=DocumentFill.White, + ) + + # Create a shape layer + shape_layer = doc.artLayers.add() + shape_layer.name = "Blue Rectangle" + + # Set shape color + color = ps.SolidColor() + color.rgb.red = 0 + color.rgb.green = 0 + color.rgb.blue = 255 + + # Create shape selection + doc.selection.select([ + [50, 50], + [750, 50], + [750, 550], + [50, 550], + ]) + + # Fill shape + ps.app.foregroundColor = color + doc.selection.fill(ps.app.foregroundColor) + doc.selection.deselect() + + # Add text layer + text_layer = doc.artLayers.add() + text_layer.kind = LayerKind.TextLayer + text_layer.name = "Sample Text" + text_layer.textItem.contents = "Thumbnail Demo" + text_layer.textItem.position = [300, 300] + text_layer.textItem.size = 48 + text_layer.textItem.font = "Microsoft YaHei" + + print(f"Created sample document: {name}") + +def create_thumbnail( + ps, + source_doc, + output_path: Optional[str] = None, + max_resolution: int = DEFAULT_MAX_RESOLUTION, + format: SUPPORTED_FORMATS = "jpg", + quality: int = DEFAULT_QUALITY, + maintain_aspect: bool = True, + background_color: Optional[tuple[int, int, int]] = None, +) -> str: """Create a thumbnail image for currently active document. Args: - output_path (str): The absolute output path of the thumbnail image. - The default is to output to a temporary folder. - max_resolution (int): The max resolution of the thumbnail. The default - is `512`. + ps: Photoshop session object + source_doc: Source document object + output_path: Output path for the thumbnail image + max_resolution: Maximum resolution for the thumbnail image + format: Output format ("jpg" or "png") + quality: JPEG quality (1-12, only for JPG) + maintain_aspect: If True, maintains aspect ratio + background_color: RGB tuple for background color Returns: - str: The absolute output path of the thumbnail image. + str: Path to the generated thumbnail image + Raises: + ValueError: If format is not supported or quality is out of range + RuntimeError: If no active document is found """ - output_path = output_path or os.path.join(mkdtemp(), "thumb.jpg") - - with Session(auto_close=True) as ps: - orig_name = ps.active_document.name - width_str = ps.active_document.width - height_str = ps.active_document.height - thumb_name = f"{orig_name}_thumb" - - max_resolution = width_str / max_resolution - thumb_width = int(width_str / max_resolution) - thumb_height = int(height_str / max_resolution) - - thumb_doc = ps.active_document.duplicate(thumb_name) - thumb_doc.resizeImage(thumb_width, thumb_height - 100) - thumb_doc.saveAs(output_path, ps.JPEGSaveOptions(), asCopy=True) - thumb_doc.close() + # Validate inputs + if format not in ["jpg", "png"]: + raise ValueError(f"Unsupported format: {format}. Use 'jpg' or 'png'") + + if format == "jpg" and not 1 <= quality <= 12: + raise ValueError("JPEG quality must be between 1 and 12") + + # Generate output path if not provided + if output_path is None: + output_dir = Path(mkdtemp()) + output_path = str(output_dir / f"thumb.{format}") + else: + # Ensure directory exists + output_dir = Path(output_path).parent + output_dir.mkdir(parents=True, exist_ok=True) + + # Create a temporary directory for intermediate files + temp_dir = Path(mkdtemp()) + temp_path = str(temp_dir / f"temp_{Path(output_path).name}") + + # Get original dimensions + width = source_doc.width + height = source_doc.height + + # Calculate new dimensions + if maintain_aspect: + ratio = width / height + if width > height: + new_width = max_resolution + new_height = max_resolution / ratio + else: + new_height = max_resolution + new_width = max_resolution * ratio + else: + new_width = new_height = max_resolution + + # Create new document + new_doc = ps.app.documents.add( + width=new_width, + height=new_height, + resolution=72.0, + mode=NewDocumentMode.NewRGB, + initialFill=DocumentFill.White, + ) + time.sleep(0.5) # Wait for document to initialize + + try: + # Set background color if specified + if background_color: + color = ps.SolidColor() + color.rgb.red = background_color[0] + color.rgb.green = background_color[1] + color.rgb.blue = background_color[2] + new_doc.backgroundColor = color + + # Use JavaScript to copy and paste + js = f""" + try {{ + var sourceDoc = app.documents.getByName("{source_doc.name}"); + var targetDoc = app.documents.getByName("{new_doc.name}"); + app.activeDocument = sourceDoc; + sourceDoc.activeLayer.copy(); + app.activeDocument = targetDoc; + targetDoc.paste(); + "success" + }} catch(e) {{ + e.message + }} + """ + result = ps.app.doJavaScript(js) + if result != "success": + print(f"Warning: Copy operation failed - {result}") + + time.sleep(0.5) # Wait for paste to complete + + # Save the thumbnail + ps.app.activeDocument = new_doc + if format == "jpg": + options = ps.JPEGSaveOptions(quality=quality) + else: # png + options = ps.PNGSaveOptions(compression=9) + + # Save with a temporary name first + new_doc.saveAs(temp_path, options, True) + time.sleep(0.5) # Wait for save to complete + + # Close and reopen + new_doc.close() + time.sleep(0.5) # Wait for close to complete + + # Open the temporary file + new_doc = ps.app.open(temp_path) + time.sleep(0.5) # Wait for document to load + + # Save with the final name + new_doc.saveAs(output_path, options, True) + time.sleep(0.5) # Wait for save to complete + + print(f"Created thumbnail: {output_path}") + print(f" Size: {new_width:.0f}x{new_height:.0f} pixels") + print(f" Format: {format.upper()}") + if format == "jpg": + print(f" Quality: {quality}") + return output_path + finally: + # Always close the new document + if os.path.exists(temp_path): + os.remove(temp_path) + new_doc.close() + time.sleep(0.5) # Wait for close to complete + # Clean up temporary directory + temp_dir.rmdir() + +def create_multiple_thumbnails( + ps, + source_path: str, + sizes: list[int], + output_dir: Optional[str] = None, + format: SUPPORTED_FORMATS = "jpg", + quality: int = DEFAULT_QUALITY, + prefix: str = "thumb", +) -> list[str]: + """Create multiple thumbnails of different sizes. + + Args: + ps: Photoshop session object + source_path: Path to source document + sizes: List of maximum resolutions + output_dir: Output directory (default: temp dir) + format: Output format ("jpg" or "png") + quality: JPEG quality (1-12, only for JPG) + prefix: Filename prefix for thumbnails + + Returns: + list[str]: List of paths to generated thumbnails + """ + if output_dir is None: + output_dir = mkdtemp() + else: + os.makedirs(output_dir, exist_ok=True) + + thumbnails = [] + source_doc = None + + try: + # Open source document + source_doc = ps.app.open(source_path) + time.sleep(0.5) # Wait for document to load + + # Ensure source document is active + ps.app.activeDocument = source_doc + time.sleep(0.5) # Wait for document to activate + + for size in sizes: + try: + output_path = os.path.join(output_dir, f"{prefix}_{size}.{format}") + thumb_path = create_thumbnail( + ps, + source_doc, + output_path=output_path, + max_resolution=size, + format=format, + quality=quality, + ) + thumbnails.append(thumb_path) + print(f"Created {size}px thumbnail: {thumb_path}") + except Exception as e: + print(f"Failed to create {size}px thumbnail: {e}") + continue + finally: + # Close source document + if source_doc: + source_doc.close() + time.sleep(0.5) # Wait for close to complete + + return thumbnails + +def main() -> None: + """Run the example script. + + This function demonstrates different thumbnail creation scenarios: + 1. Basic thumbnail with default settings + 2. Custom thumbnail with specific format and quality + 3. Multiple thumbnails of different sizes + """ + # Try different Photoshop versions + versions = ["2024", "2023", "2022", "2021", "2020"] + connected = False + + for version in versions: + try: + with Session(ps_version=version) as ps: + print(f"Successfully connected to Photoshop {version}") + connected = True + + # Store original ruler units + app = ps.app + start_ruler_units = app.preferences.rulerUnits + app.preferences.rulerUnits = Units.Pixels + + try: + # Create a sample document + create_sample_document(ps) + source_doc = ps.active_document + + # Save sample document + temp_dir = mkdtemp() + source_path = os.path.join(temp_dir, "sample.psd") + options = ps.PhotoshopSaveOptions() + source_doc.saveAs(source_path, options) + time.sleep(0.5) # Wait for save to complete + source_doc.close() + time.sleep(0.5) # Wait for close to complete + + # Example 1: Basic thumbnail + source_doc = ps.app.open(source_path) + time.sleep(0.5) # Wait for document to load + try: + thumb_file = create_thumbnail(ps, source_doc) + print(f"\nBasic thumbnail saved to: {thumb_file}") + finally: + source_doc.close() + time.sleep(0.5) # Wait for close to complete + + # Example 2: Custom thumbnail (PNG with blue background) + source_doc = ps.app.open(source_path) + time.sleep(0.5) # Wait for document to load + try: + custom_thumb = create_thumbnail( + ps, + source_doc, + output_path="custom_thumb.png", + max_resolution=256, + format="png", + background_color=(0, 0, 255), + ) + print(f"\nCustom thumbnail saved to: {custom_thumb}") + finally: + source_doc.close() + time.sleep(0.5) # Wait for close to complete + + # Example 3: Multiple thumbnails + print("\nCreating multiple thumbnails...") + thumbs = create_multiple_thumbnails( + ps, + source_path, + sizes=[128, 256], # 减少尺寸数量 + output_dir="thumbnails", + format="jpg", + quality=12, + prefix="photo", + ) + print("\nCreated thumbnails:") + for thumb in thumbs: + print(f"- {thumb}") + + finally: + # Restore ruler units + app.preferences.rulerUnits = start_ruler_units + break # Exit the loop if successful + except Exception as e: + print(f"Failed to connect to Photoshop {version}: {e}") + continue + + if not connected: + print("\nFailed to connect to any supported Photoshop version.") + print("Please make sure Photoshop is installed and running correctly.") + print("Supported versions: " + ", ".join(versions)) if __name__ == "__main__": - thumb_file = create_thumbnail() - print(f"Save thumbnail file to {thumb_file}.") + main() diff --git a/examples/creating_a_layer.py b/examples/creating_a_layer.py index 214499c6..9e39de1c 100644 --- a/examples/creating_a_layer.py +++ b/examples/creating_a_layer.py @@ -1,27 +1,293 @@ -""" -Let's get the current document and create a new layer "Background" and fill it -with red color. In order to use the Fill tool we will first select the entire -layer and then fill it with a color. +"""Example of creating and manipulating layers in Photoshop. +This script demonstrates various layer operations: +1. Creating different types of layers (normal, text, shape) +2. Setting layer properties (opacity, blend mode) +3. Working with layer groups +4. Applying layer effects """ +# Import built-in modules +from __future__ import annotations +import os +from tempfile import mkdtemp + # Import local modules from photoshop import Session +from photoshop.api import enumerations + +def create_sample_document(ps, width=800, height=600, name="Layer Example"): + """Create a sample document for layer operations. + + Args: + ps: Photoshop session object + width: Document width in pixels + height: Document height in pixels + name: Document name + """ + doc = ps.app.documents.add( + width=width, + height=height, + resolution=72.0, + name=name, + mode=enumerations.NewDocumentMode.NewRGB, + initialFill=enumerations.DocumentFill.White, + ) + return doc +def create_background_layer(ps, document, color=(222, 0, 0)): + """Create a background layer with specified color. -with Session() as ps: - document = ps.active_document - # Create color object of color red. - fillColor = ps.SolidColor() - fillColor.rgb.red = 222 - fillColor.rgb.green = 0 - fillColor.rgb.blue = 0 - # Add a new layer called Background. + Args: + ps: Photoshop session object + document: Target document + color: RGB color tuple + """ + # Create color object + fill_color = ps.SolidColor() + fill_color.rgb.red = color[0] + fill_color.rgb.green = color[1] + fill_color.rgb.blue = color[2] + + # Add background layer layer = document.artLayers.add() layer.name = "Background" - # Select the entire layer. + + # Fill layer with color document.selection.selectAll() - # Fill the selection with color. - document.selection.fill(fillColor) - # Deselect. + document.selection.fill(fill_color) document.selection.deselect() + + return layer + +def create_text_layer(ps, document, text="Sample Text", position=(100, 100)): + """Create a text layer with specified text and position. + + Args: + ps: Photoshop session object + document: Target document + text: Text content + position: (x, y) position tuple + """ + # Add text layer + text_layer = document.artLayers.add() + text_layer.kind = enumerations.LayerKind.TextLayer + text_layer.name = "Text Layer" + + # Set text properties + text_item = text_layer.textItem + text_item.contents = text + text_item.position = position + text_item.size = 48 # Point size + + # Set text color using JavaScript + js_code = """ + try { + var doc = app.activeDocument; + var layer = doc.layers.getByName("Text Layer"); + var color = new SolidColor(); + color.rgb.red = 255; + color.rgb.green = 255; + color.rgb.blue = 255; + layer.textItem.color = color; + "success" + } catch(e) { + e.message + } + """ + result = ps.app.doJavaScript(js_code) + if result != "success": + print(f"Warning: Text color setting failed - {result}") + + return text_layer + +def create_shape_layer(ps, document): + """Create a shape layer with a rectangle. + + Args: + ps: Photoshop session object + document: Target document + """ + # Draw rectangle using JavaScript + js_code = """ + try { + var doc = app.activeDocument; + + // Create a new shape layer + var layer = doc.artLayers.add(); + layer.name = "Shape Layer"; + + // Set layer properties + var color = new SolidColor(); + color.rgb.red = 0; + color.rgb.green = 255; + color.rgb.blue = 0; + + // Create rectangle shape + var startRulerUnits = app.preferences.rulerUnits; + var startTypeUnits = app.preferences.typeUnits; + app.preferences.rulerUnits = Units.PIXELS; + app.preferences.typeUnits = TypeUnits.PIXELS; + + // Create shape + var shapeRef = [ + [300, 200], + [500, 200], + [500, 400], + [300, 400] + ]; + + // Create the shape + doc.selection.select(shapeRef); + doc.selection.fill(color); + doc.selection.deselect(); + + // Restore preferences + app.preferences.rulerUnits = startRulerUnits; + app.preferences.typeUnits = startTypeUnits; + + "success" + } catch(e) { + e.message + } + """ + result = ps.app.doJavaScript(js_code) + if result != "success": + print(f"Warning: Shape creation failed - {result}") + return None + + # Get the created layer + shape_layer = document.artLayers.getByName("Shape Layer") + return shape_layer + +def create_layer_group(document, name="Layer Group"): + """Create a layer group. + + Args: + document: Target document + name: Group name + """ + group = document.layerSets.add() + group.name = name + return group + +def apply_layer_style(ps, layer): + """Apply some layer styles. + + Args: + ps: Photoshop session object + layer: Target layer + """ + # Apply drop shadow using JavaScript + js_code = """ + try { + var doc = app.activeDocument; + var layer = doc.activeLayer; + + // Create drop shadow + var idsetd = charIDToTypeID("setd"); + var desc = new ActionDescriptor(); + var idnull = charIDToTypeID("null"); + var ref = new ActionReference(); + var idPrpr = charIDToTypeID("Prpr"); + var idLefx = charIDToTypeID("Lefx"); + ref.putProperty(idPrpr, idLefx); + ref.putEnumerated(charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt")); + desc.putReference(idnull, ref); + var idT = charIDToTypeID("T "); + var desc2 = new ActionDescriptor(); + var idScl = charIDToTypeID("Scl "); + desc2.putUnitDouble(idScl, charIDToTypeID("#Prc"), 100); + var idDrSh = charIDToTypeID("DrSh"); + var desc3 = new ActionDescriptor(); + var idenab = charIDToTypeID("enab"); + desc3.putBoolean(idenab, true); + var idMd = charIDToTypeID("Md "); + var idBlnM = charIDToTypeID("BlnM"); + var idMltp = charIDToTypeID("Mltp"); + desc3.putEnumerated(idMd, idBlnM, idMltp); + var idClr = charIDToTypeID("Clr "); + var desc4 = new ActionDescriptor(); + var idRd = charIDToTypeID("Rd "); + desc4.putDouble(idRd, 0); + var idGrn = charIDToTypeID("Grn "); + desc4.putDouble(idGrn, 0); + var idBl = charIDToTypeID("Bl "); + desc4.putDouble(idBl, 0); + var idRGBC = charIDToTypeID("RGBC"); + desc3.putObject(idClr, idRGBC, desc4); + var idOpct = charIDToTypeID("Opct"); + desc3.putUnitDouble(idOpct, charIDToTypeID("#Prc"), 75); + var iduglg = charIDToTypeID("uglg"); + desc3.putBoolean(iduglg, true); + var idlagl = charIDToTypeID("lagl"); + desc3.putDouble(idlagl, 120); + var idDstn = charIDToTypeID("Dstn"); + desc3.putUnitDouble(idDstn, charIDToTypeID("#Pxl"), 10); + var idCkmt = charIDToTypeID("Ckmt"); + desc3.putUnitDouble(idCkmt, charIDToTypeID("#Pxl"), 0); + var idblur = charIDToTypeID("blur"); + desc3.putUnitDouble(idblur, charIDToTypeID("#Pxl"), 10); + var idNose = charIDToTypeID("Nose"); + desc3.putUnitDouble(idNose, charIDToTypeID("#Prc"), 0); + var idAntA = charIDToTypeID("AntA"); + desc3.putBoolean(idAntA, false); + desc2.putObject(idDrSh, idDrSh, desc3); + var idLefx = charIDToTypeID("Lefx"); + desc.putObject(idT, idLefx, desc2); + executeAction(idsetd, desc, DialogModes.NO); + "success" + } catch(e) { + e.message + } + """ + # Make sure the layer is active + ps.app.activeDocument.activeLayer = layer + + # Apply the style + result = ps.app.doJavaScript(js_code) + if result != "success": + print(f"Warning: Layer style application failed - {result}") + +def main(): + """Run the example script.""" + with Session() as ps: + try: + # Create new document + doc = create_sample_document(ps) + print("Created new document") + + # Create background layer + bg_layer = create_background_layer(ps, doc) + print("Created background layer") + + # Create layer group + group = create_layer_group(doc, "Content") + print("Created layer group") + + # Create text layer + text_layer = create_text_layer(ps, doc) + text_layer.move(group, enumerations.ElementPlacement.PlaceInside) + print("Created text layer") + + # Create shape layer + shape_layer = create_shape_layer(ps, doc) + shape_layer.move(group, enumerations.ElementPlacement.PlaceInside) + print("Created shape layer") + + # Apply layer styles + apply_layer_style(ps, text_layer) + print("Applied layer styles") + + # Save the document + temp_dir = mkdtemp() + output_path = os.path.join(temp_dir, "layer_example.psd") + doc.saveAs(output_path, ps.PhotoshopSaveOptions()) + print(f"\nDocument saved to: {output_path}") + + finally: + if doc: + doc.close() + +if __name__ == "__main__": + main() diff --git a/examples/cropping.py b/examples/cropping.py index 92d1f4c1..507eb20e 100644 --- a/examples/cropping.py +++ b/examples/cropping.py @@ -1,8 +1,181 @@ -"""A cropping example.""" +"""Demonstrates various image cropping techniques in Photoshop. + +This example shows different ways to crop images, including: +1. Basic cropping with bounds +2. Cropping with aspect ratio +3. Cropping with resolution +4. Center-based cropping +""" + +from __future__ import annotations + +import os +from tempfile import mkdtemp -# Import local modules from photoshop import Session +def create_sample_document(ps, width=800, height=600): + """Create a sample document with some content. + + Args: + ps: Photoshop session object + width: Document width in pixels + height: Document height in pixels + + Returns: + The created document + """ + # Create a new document + doc = ps.app.documents.add( + width=width, + height=height, + resolution=72, + name="Crop Example", + ) + + # Add background content using JavaScript + js_code = """ + try { + var doc = app.activeDocument; + + // Create a solid color layer + var layer = doc.artLayers.add(); + layer.name = "Background"; + + // Set color + var color = new SolidColor(); + color.rgb.red = 255; + color.rgb.green = 200; + color.rgb.blue = 0; + + // Fill the layer + doc.selection.selectAll(); + doc.selection.fill(color); + doc.selection.deselect(); + + "success" + } catch(e) { + e.message + } + """ + result = ps.app.doJavaScript(js_code) + if result != "success": + print(f"Warning: Background creation failed - {result}") + + return doc + +def crop_with_bounds(ps, doc): + """Crop the document using bounds. + + Args: + ps: Photoshop session object + doc: Target document + """ + # Crop from all sides by 100 pixels + doc.crop([100, 100, doc.width - 100, doc.height - 100]) + print("Applied bounds-based crop") + +def crop_with_aspect_ratio(ps, doc): + """Crop the document to a specific aspect ratio. + + Args: + ps: Photoshop session object + doc: Target document + """ + # Crop to 16:9 aspect ratio + target_width = doc.width + target_height = int(target_width * 9 / 16) + + # Calculate vertical offset to center the crop + y_offset = (doc.height - target_height) // 2 + + doc.crop([0, y_offset, target_width, y_offset + target_height]) + print("Applied aspect ratio crop (16:9)") + +def crop_with_resolution(ps, doc): + """Crop the document and change its resolution. + + Args: + ps: Photoshop session object + doc: Target document + """ + # First set the resolution + doc.resolution = 300 + + # Then crop and resize + doc.crop( + bounds=[0, 0, doc.width, doc.height], + width=1920, + height=1080, + ) + print("Applied resolution crop (1920x1080 @ 300dpi)") + +def center_crop(ps, doc, target_size): + """Crop the document from the center. + + Args: + ps: Photoshop session object + doc: Target document + target_size: Tuple of (width, height) for the target size + """ + target_width, target_height = target_size + + # Calculate offsets to center the crop + x_offset = (doc.width - target_width) // 2 + y_offset = (doc.height - target_height) // 2 + + doc.crop([ + x_offset, + y_offset, + x_offset + target_width, + y_offset + target_height, + ]) + print(f"Applied center crop to {target_width}x{target_height}") + +def main(): + """Run the example script.""" + with Session() as ps: + doc = None + try: + # Create sample document + doc = create_sample_document(ps) + print("Created sample document") + + # Save original + temp_dir = mkdtemp() + original_path = os.path.join(temp_dir, "original.psd") + doc.saveAs(original_path, ps.PhotoshopSaveOptions()) + print(f"Saved original to: {original_path}") + + # Try different crop methods + crop_methods = [ + ("bounds", lambda: crop_with_bounds(ps, doc)), + ("aspect", lambda: crop_with_aspect_ratio(ps, doc)), + ("resolution", lambda: crop_with_resolution(ps, doc)), + ("center", lambda: center_crop(ps, doc, (400, 400))), + ] + + for name, crop_func in crop_methods: + # Create new document for each crop + if doc: + doc.close() + doc = create_sample_document(ps) + + # Apply crop + crop_func() + + # Save result + output_path = os.path.join(temp_dir, f"crop_{name}.psd") + doc.saveAs(output_path, ps.PhotoshopSaveOptions()) + print(f"Saved {name} crop to: {output_path}") + + finally: + # Close the last document if it exists + try: + if doc and ps.app.documents.length > 0: + doc.close() + except Exception as e: + print(f"Warning: Error while closing document - {e}") -with Session(action="new_document") as ps: - ps.active_document.crop(bounds=[100, 12, 354, 246], width=1920, height=1080) +if __name__ == "__main__": + main() diff --git a/examples/current_tool.py b/examples/current_tool.py index a79d235e..f8b5db1e 100644 --- a/examples/current_tool.py +++ b/examples/current_tool.py @@ -1,10 +1,134 @@ -# Import local modules +"""Demonstrates how to work with Photoshop tools. + +This example shows how to: +1. Get the current tool +2. Change tools +3. List available tools +4. Set tool options +""" + +from __future__ import annotations + +import os +from tempfile import mkdtemp + from photoshop import Session +def print_current_tool(ps): + """Print information about the current tool. + + Args: + ps: Photoshop session object + """ + current_tool = ps.app.currentTool + print(f"Current tool: {current_tool}") + +def set_tool(ps, tool_name): + """Set the current tool. + + Args: + ps: Photoshop session object + tool_name: Name of the tool to set + """ + try: + ps.app.currentTool = tool_name + print(f"Changed to tool: {tool_name}") + except Exception as e: + print(f"Error setting tool {tool_name}: {e}") + +def create_sample_document(ps): + """Create a sample document to work with. + + Args: + ps: Photoshop session object + + Returns: + The created document + """ + # Create new document + doc = ps.app.documents.add( + width=800, + height=600, + resolution=72, + name="Tool Example", + ) + + # Add a layer to work with + layer = doc.artLayers.add() + layer.name = "Test Layer" + + return doc + +def set_tool_options(ps, doc): + """Set various tool options. + + Args: + ps: Photoshop session object + doc: Target document + """ + # Set ruler units to pixels + js_code = """ + try { + app.preferences.rulerUnits = Units.PIXELS; + app.preferences.typeUnits = TypeUnits.PIXELS; + "success" + } catch(e) { e.message } + """ + result = ps.app.doJavaScript(js_code) + print(f"Set preferences: {result}") + + # Set layer opacity + try: + doc.activeLayer.opacity = 50 + print("Set layer opacity to 50%") + except Exception as e: + print(f"Error setting opacity: {e}") -with Session() as ps: - # Print the current tool. - ps.echo(ps.app.currentTool) +def main(): + """Run the example script.""" + with Session() as ps: + try: + # Create sample document + doc = create_sample_document(ps) + print("Created sample document") + + # Print current tool + print_current_tool(ps) + + # Try different tools + tools = [ + "moveTool", # Move tool + "marqueeRectTool", # Rectangular marquee tool + "marqueeEllipTool", # Elliptical marquee tool + "lassoTool", # Lasso tool + "magicWandTool", # Magic wand tool + "cropTool", # Crop tool + "eyedropperTool", # Eyedropper tool + "paintbrushTool", # Brush tool + "eraserTool", # Eraser tool + "typeCreateOrEditTool", # Type tool + ] + + print("\nTrying different tools:") + for tool in tools: + set_tool(ps, tool) + + # Set tool options + print("\nSetting tool options:") + set_tool_options(ps, doc) + + # Save the document + temp_dir = mkdtemp() + output_path = os.path.join(temp_dir, "tool_example.psd") + doc.saveAs(output_path, ps.PhotoshopSaveOptions()) + print(f"\nDocument saved to: {output_path}") + + finally: + if doc: + try: + doc.close() + except Exception as e: + print(f"Warning: Error while closing document - {e}") - # Set current tool to `typeCreateOrEditTool`. - ps.app.currentTool = "typeCreateOrEditTool" +if __name__ == "__main__": + main() diff --git a/examples/do_photoshop_action.py b/examples/do_photoshop_action.py index 111e93d2..aaf6c741 100644 --- a/examples/do_photoshop_action.py +++ b/examples/do_photoshop_action.py @@ -1,7 +1,138 @@ -"""Do a photoshop action.""" -# Import local modules +"""Demonstrates how to work with Photoshop actions. + +This example shows how to: +1. List available actions +2. Run actions +3. Apply text watermark directly +""" + +from __future__ import annotations + +import os +from tempfile import mkdtemp + from photoshop import Session +from photoshop.api import enumerations + +def list_actions(ps): + """List available actions and action sets. + + Args: + ps: Photoshop session object + """ + js_code = """ + try { + var result = []; + // List action sets + for (var i = 0; i < app.actionSets.length; i++) { + var set = app.actionSets[i]; + result.push("Action Set: " + set.name); + + // List actions in the set + for (var j = 0; j < set.actions.length; j++) { + var action = set.actions[j]; + result.push(" - Action: " + action.name); + } + } + result.join("\\n"); + } catch(e) { + e.message; + } + """ + result = ps.app.doJavaScript(js_code) + print("\nAvailable Actions:") + print(result if result else "No actions found") + +def add_watermark(ps, doc, text="Watermark"): + """Add a text watermark to the document. + + Args: + ps: Photoshop session object + doc: Target document + text: Watermark text + """ + try: + # Create text layer + layer = doc.artLayers.add() + layer.kind = enumerations.LayerKind.TextLayer + + # Set text properties + text_item = layer.textItem + text_item.contents = text + text_item.size = 48 # points + + # Set color to white + text_color = ps.SolidColor() + text_color.rgb.red = 255 + text_color.rgb.green = 255 + text_color.rgb.blue = 255 + text_item.color = text_color + + # Set opacity + layer.opacity = 50 + + # Center the text + text_item.position = [doc.width/2, doc.height/2] + + print(f"Added watermark: {text}") + except Exception as e: + print(f"Error adding watermark: {e}") + +def create_sample_document(ps): + """Create a sample document to work with. + + Args: + ps: Photoshop session object + + Returns: + The created document + """ + # Create new document + doc = ps.app.documents.add( + width=800, + height=600, + resolution=72, + name="Action Example", + ) + + # Add some content (colored background) + color = ps.SolidColor() + color.rgb.red = 0 + color.rgb.green = 100 + color.rgb.blue = 200 + + doc.selection.selectAll() + doc.selection.fill(color) + doc.selection.deselect() + + return doc +def main(): + """Run the example script.""" + with Session() as ps: + try: + # Create sample document + doc = create_sample_document(ps) + print("Created sample document") + + # List available actions + list_actions(ps) + + # Add watermark directly + add_watermark(ps, doc, "Sample Watermark") + + # Save the document + temp_dir = mkdtemp() + output_path = os.path.join(temp_dir, "watermark_example.psd") + doc.saveAs(output_path, ps.PhotoshopSaveOptions()) + print(f"\nDocument saved to: {output_path}") + + finally: + if doc: + try: + doc.close() + except Exception as e: + print(f"Warning: Error while closing document - {e}") -with Session() as api: - api.app.doAction(action="Frame Channel - 50 pixel") +if __name__ == "__main__": + main() diff --git a/examples/emboss_action.py b/examples/emboss_action.py index 76bee439..aa455887 100644 --- a/examples/emboss_action.py +++ b/examples/emboss_action.py @@ -1,62 +1,155 @@ -# Import local modules +"""Example of applying emboss effect in Photoshop. + +This script demonstrates how to: +1. Create a sample document +2. Create a solid color fill layer +3. Apply emboss effect +4. Save the result +""" + +from __future__ import annotations + +import os +from tempfile import mkdtemp +from typing import Any + from photoshop import Session +def create_sample_document(ps: Session) -> Any: + """Create a sample document to work with. + + Args: + ps: Photoshop session instance + + Returns: + The created document + """ + # Create new document + doc = ps.app.documents.add( + width=800, + height=600, + resolution=72, + name="Emboss Example", + ) + return doc + +def create_color_fill_layer(ps: Session, color: tuple[int, int, int]) -> None: + """Create a solid color fill layer with specified RGB values. + + Args: + ps: Photoshop session instance + color: Tuple of (red, green, blue) values (0-255) + """ + js_code = f""" + try {{ + // Create solid color fill layer + var doc = app.activeDocument; + var layer = doc.artLayers.add(); + + // Create color + var solidColor = new SolidColor(); + solidColor.rgb.red = {color[0]}; + solidColor.rgb.green = {color[1]}; + solidColor.rgb.blue = {color[2]}; + + // Fill layer with color + doc.selection.selectAll(); + doc.selection.fill(solidColor); + doc.selection.deselect(); + + "success" + }} catch(e) {{ + e.message + }} + """ + result = ps.app.doJavaScript(js_code) + if result == "success": + print("Created color fill layer") + else: + print(f"Error creating color fill layer: {result}") + +def apply_emboss_effect(ps: Session) -> None: + """Apply emboss effect to the current layer. + + Args: + ps: Photoshop session instance + """ + js_code = """ + try { + // Get the active layer + var doc = app.activeDocument; + var layer = doc.activeLayer; + + // Apply emboss effect + var desc = new ActionDescriptor(); + var ref = new ActionReference(); + ref.putProperty(charIDToTypeID("Prpr"), stringIDToTypeID("layerEffects")); + ref.putEnumerated(charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt")); + desc.putReference(charIDToTypeID("null"), ref); + + var edesc = new ActionDescriptor(); + var effects = new ActionDescriptor(); + var bevl = new ActionDescriptor(); + + bevl.putEnumerated(stringIDToTypeID("bevelStyle"), stringIDToTypeID("bevelStyle"), stringIDToTypeID("emboss")); + bevl.putEnumerated(stringIDToTypeID("bevelTechnique"), stringIDToTypeID("bevelTechnique"), stringIDToTypeID("softMatte")); + bevl.putUnitDouble(stringIDToTypeID("depth"), charIDToTypeID("#Prc"), 100); + bevl.putUnitDouble(stringIDToTypeID("size"), charIDToTypeID("#Pxl"), 10); + bevl.putUnitDouble(stringIDToTypeID("angle"), charIDToTypeID("#Ang"), 45); + bevl.putUnitDouble(stringIDToTypeID("altitude"), charIDToTypeID("#Ang"), 30); + bevl.putBoolean(stringIDToTypeID("useGlobalLight"), true); + bevl.putUnitDouble(stringIDToTypeID("soften"), charIDToTypeID("#Pxl"), 0); + + effects.putObject(stringIDToTypeID("bevelEmboss"), stringIDToTypeID("bevelEmboss"), bevl); + edesc.putObject(stringIDToTypeID("layerEffects"), stringIDToTypeID("layerEffects"), effects); + desc.putObject(charIDToTypeID("T "), stringIDToTypeID("layerEffects"), edesc); + + executeAction(charIDToTypeID("setd"), desc, DialogModes.NO); + + "success" + } catch(e) { + e.message + } + """ + result = ps.app.doJavaScript(js_code) + if result == "success": + print("Applied emboss effect") + else: + print(f"Error applying emboss effect: {result}") + +def main() -> None: + """Create a document with embossed effect. + + This function demonstrates how to: + 1. Create a sample document + 2. Create a solid color fill layer + 3. Apply emboss effect + 4. Save the result + """ + with Session() as ps: + try: + # Create sample document + doc = create_sample_document(ps) + print("Created sample document") + + # Create color fill layer + create_color_fill_layer(ps, (100, 150, 200)) + + # Apply emboss effect + apply_emboss_effect(ps) + + # Save the document + temp_dir = mkdtemp() + output_path = os.path.join(temp_dir, "emboss_example.psd") + doc.saveAs(output_path, ps.PhotoshopSaveOptions()) + print(f"\nDocument saved to: {output_path}") + + finally: + if doc: + try: + doc.close() + except Exception as e: + print(f"Warning: Error while closing document - {e}") -with Session() as ps: - app = ps.app - for index, x in enumerate(range(50)): - # Execute an existing action from action palette. - idPly = app.charIDToTypeID("Ply ") - desc8 = ps.ActionDescriptor() - idnull = app.charIDToTypeID("null") - ref3 = ps.ActionReference() - idActn = app.charIDToTypeID("Actn") - ref3.putName(idActn, "Sepia Toning (layer)") - idASet = app.charIDToTypeID("ASet") - ref3.PutName(idASet, "Default Actions") - desc8.putReference(idnull, ref3) - app.executeAction(idPly, desc8, ps.DialogModes.DisplayNoDialogs) - - # Create solid color fill layer. - idMk = app.charIDToTypeID("Mk ") - desc21 = ps.ActionDescriptor() - idNull = app.charIDToTypeID("null") - ref12 = ps.ActionReference() - idContentLayer1 = app.stringIDToTypeID("contentLayer") - ref12.putClass(idContentLayer1) - desc21.putReference(idNull, ref12) - idUsng = app.charIDToTypeID("Usng") - desc22 = ps.ActionDescriptor() - idType = app.charIDToTypeID("Type") - desc23 = ps.ActionDescriptor() - idClr = app.charIDToTypeID("Clr ") - desc24 = ps.ActionDescriptor() - idRd = app.charIDToTypeID("Rd ") - desc24.putDouble(idRd, index) - idGrn = app.charIDToTypeID("Grn ") - desc24.putDouble(idGrn, index) - idBl = app.charIDToTypeID("Bl ") - desc24.putDouble(idBl, index) - idRGBC = app.charIDToTypeID("RGBC") - desc23.putObject(idClr, idRGBC, desc24) - idSolidColorLayer = app.StringIDToTypeID("solidColorLayer") - desc22.putObject(idType, idSolidColorLayer, desc23) - idContentLayer2 = app.StringIDToTypeID("contentLayer") - desc21.putObject(idUsng, idContentLayer2, desc22) - app.executeAction(idMk, desc21, ps.DialogModes.DisplayNoDialogs) - - # Select mask. - idSlct = app.charIDToTypeID("slct") - desc38 = ps.ActionDescriptor() - idNull1 = app.charIDToTypeID("null") - ref20 = ps.ActionReference() - idChnl1 = app.charIDToTypeID("Chnl") - idChnl2 = app.charIDToTypeID("Chnl") - idMsk = app.charIDToTypeID("Msk ") - ref20.putEnumerated(idChnl1, idChnl2, idMsk) - desc38.putReference(idNull1, ref20) - idMkVs = app.charIDToTypeID("MkVs") - desc38.putBoolean(idMkVs, False) - app.executeAction(idSlct, desc38, ps.DialogModes.DisplayNoDialogs) - - app.activeDocument.activeLayer.invert() +if __name__ == "__main__": + main() diff --git a/examples/enable_generator.py b/examples/enable_generator.py index 9a2750e1..bef5d121 100644 --- a/examples/enable_generator.py +++ b/examples/enable_generator.py @@ -1,10 +1,103 @@ -"""Enable Generator features.""" -# Import local modules +"""Example of enabling and managing Photoshop Generator features. + +This script demonstrates how to: +1. Enable Generator functionality +2. Configure Generator settings +3. Handle Generator-related errors +""" + +from __future__ import annotations + + from photoshop import Session +def enable_generator(ps: Session, plugin_name: str = "generator-assets") -> bool: + """Enable Generator functionality with specified plugin. + + Args: + ps: Photoshop session instance + plugin_name: Name of the Generator plugin to enable + + Returns: + bool: True if Generator was enabled successfully, False otherwise + """ + try: + # Create Generator descriptor + generator_desc = ps.ActionDescriptor + generator_desc.putString(ps.app.stringIDToTypeID("name"), plugin_name) + + # Execute Generator action + ps.app.executeAction(ps.app.stringIDToTypeID("generateAssets"), generator_desc) + print(f"Successfully enabled Generator with plugin: {plugin_name}") + return True + except Exception as e: + print(f"Error enabling Generator: {e}") + return False + +def configure_generator_settings(ps: Session) -> bool: + """Configure Generator settings using JavaScript. + + Args: + ps: Photoshop session instance + + Returns: + bool: True if settings were configured successfully, False otherwise + """ + try: + js_code = """ + try { + // Configure Generator settings using JavaScript + app.preferences.rulerUnits = Units.PIXELS; + app.preferences.typeUnits = TypeUnits.PIXELS; + app.preferences.exportClipboard = true; + + // Configure Generator-specific preferences + var desc = new ActionDescriptor(); + desc.putBoolean(stringIDToTypeID("svg-enabled"), true); + desc.putInteger(stringIDToTypeID("jpg-quality"), 90); + desc.putBoolean(stringIDToTypeID("png-interlaced"), false); + desc.putBoolean(stringIDToTypeID("use-smart-scaling"), true); + desc.putBoolean(stringIDToTypeID("include-ancestor-masks"), true); + app.putCustomOptions("generator-assets-configuration", desc); + + "success" + } catch(e) { + e.message + } + """ + result = ps.app.doJavaScript(js_code) + if result == "success": + print("Successfully configured Generator settings") + return True + print(f"Error configuring Generator settings: {result}") + return False + except Exception as e: + print(f"Error configuring Generator settings: {e}") + return False + +def main() -> None: + """Enable and configure Generator functionality. + + This function demonstrates how to: + 1. Enable Generator + 2. Configure Generator settings + """ + with Session() as ps: + try: + # Enable Generator + if enable_generator(ps): + print("Generator enabled successfully") + + # Configure Generator settings + if configure_generator_settings(ps): + print("Generator settings configured successfully") + else: + print("Failed to configure Generator settings") + else: + print("Failed to enable Generator") + + except Exception as e: + print(f"Error in Generator management: {e}") -with Session() as ps: - plugin_name = "generator-assets-dummy-menu" - generatorDesc = ps.ActionDescriptor - generatorDesc.putString(ps.app.stringIDToTypeID("name"), plugin_name) - ps.app.executeAction(ps.app.stringIDToTypeID("generateAssets"), generatorDesc) +if __name__ == "__main__": + main() diff --git a/examples/eval_javascript.py b/examples/eval_javascript.py index 3dedf95e..c702c98f 100644 --- a/examples/eval_javascript.py +++ b/examples/eval_javascript.py @@ -1,15 +1,224 @@ -# Import local modules -import photoshop.api as ps +"""Example of executing JavaScript code in Photoshop. - -app = ps.Application() -jsx = r""" -var doc = app.activeDocument; -var orig_name = doc.name; -alert(orig_name); +This script demonstrates how to: +1. Execute JavaScript code in Photoshop +2. Handle JavaScript execution results +3. Work with Photoshop objects through JavaScript +4. Handle errors in JavaScript execution """ -app.doJavaScript(jsx) +from __future__ import annotations + +import os +from typing import Any, Optional + +from photoshop import Session + +def execute_javascript(ps: Session, js_code: str) -> Optional[Any]: + """Execute JavaScript code in Photoshop. + + Args: + ps: Photoshop session instance + js_code: JavaScript code to execute + + Returns: + Optional[Any]: Result of JavaScript execution if successful, None otherwise + """ + try: + # Add error handling wrapper + wrapped_code = f""" + try {{ + {js_code} + }} catch(e) {{ + "Error: " + e.message + }} + """ + result = ps.app.doJavaScript(wrapped_code) + + # Check if result is an error message + if isinstance(result, str) and result.startswith("Error: "): + print(f"JavaScript execution error: {result[7:]}") + return None + + return result + except Exception as e: + print(f"Error executing JavaScript: {e}") + return None + +def get_document_info(ps: Session) -> Optional[dict[str, Any]]: + """Get information about the active document. + + Args: + ps: Photoshop session instance + + Returns: + Optional[dict[str, Any]]: Document information if successful, None otherwise + """ + js_code = """ + // Get active document info + var doc = app.activeDocument; + var info = {}; + info.name = doc.name; + info.width = doc.width.value; + info.height = doc.height.value; + info.resolution = doc.resolution; + info.mode = doc.mode.toString(); + info.layerCount = doc.layers.length; + info.saved = doc.saved; + + if (doc.saved && doc.fullName) { + info.path = doc.fullName.fsName; + } + + // Convert info object to string + var result = ""; + for (var key in info) { + if (info.hasOwnProperty(key)) { + result += key + ":" + info[key] + "\\n"; + } + } + result + """ + result = execute_javascript(ps, js_code) + if result: + try: + # Parse the result string into a dictionary + info = {} + for line in result.strip().split("\n"): + key, value = line.split(":", 1) + info[key] = value + return info + except Exception as e: + print(f"Error parsing document info: {e}") + return None + +def modify_document(ps: Session, new_name: Optional[str] = None) -> bool: + """Modify the active document. + + Args: + ps: Photoshop session instance + new_name: New name for the document (optional) + + Returns: + bool: True if modification was successful, False otherwise + """ + if new_name: + js_code = f""" + // Modify document + var doc = app.activeDocument; + doc.name = "{new_name}"; + "success" + """ + result = execute_javascript(ps, js_code) + return result == "success" + return False + +def create_text_layer(ps: Session, text: str, font_size: int = 72) -> bool: + """Create a text layer in the active document. + + Args: + ps: Photoshop session instance + text: Text content + font_size: Font size in points + + Returns: + bool: True if text layer was created successfully, False otherwise + """ + js_code = f""" + // Create text layer + var doc = app.activeDocument; + var layer = doc.artLayers.add(); + layer.kind = LayerKind.TEXT; + var textItem = layer.textItem; + textItem.contents = "{text}"; + textItem.size = {font_size}; + textItem.position = [doc.width.value/2, doc.height.value/2]; + textItem.justification = Justification.CENTER; + layer.name = "Python Text Layer"; + "success" + """ + result = execute_javascript(ps, js_code) + return result == "success" + +def save_document(ps: Session, file_path: str) -> bool: + """Save the active document. + + Args: + ps: Photoshop session instance + file_path: Path to save the document + + Returns: + bool: True if document was saved successfully, False otherwise + """ + js_code = f""" + try {{ + var doc = app.activeDocument; + var file = new File("{file_path}"); + var opts = new PhotoshopSaveOptions(); + doc.saveAs(file, opts, true); + "success" + }} catch(e) {{ + e.message + }} + """ + result = execute_javascript(ps, js_code) + return result == "success" + +def main() -> None: + """Demonstrate JavaScript execution in Photoshop. + + This function shows how to: + 1. Get document information + 2. Modify document properties + 3. Create text layers + 4. Save documents + 5. Handle errors + """ + with Session() as ps: + try: + # Create a new document if none exists + js_code = """ + if (!app.documents.length) { + app.documents.add(800, 600, 72, "JavaScript Demo"); + } + "success" + """ + if execute_javascript(ps, js_code) != "success": + print("Failed to create or verify document") + return + + # Get initial document info + print("\nInitial Document Information:") + doc_info = get_document_info(ps) + if doc_info: + for key, value in doc_info.items(): + print(f"{key}: {value}") + + # Modify document + if modify_document(ps, "Modified Document"): + print("\nDocument renamed successfully") + + # Create text layer + if create_text_layer(ps, "Hello from Python!", 72): + print("Text layer created successfully") + + # Save document + temp_dir = os.path.join(os.environ.get("TEMP", ""), "photoshop_demo") + os.makedirs(temp_dir, exist_ok=True) + file_path = os.path.join(temp_dir, "javascript_demo.psd") + + if save_document(ps, file_path.replace("\\", "/")): + print(f"\nDocument saved to: {file_path}") + + # Get final document info + print("\nFinal Document Information:") + doc_info = get_document_info(ps) + if doc_info: + for key, value in doc_info.items(): + print(f"{key}: {value}") + + except Exception as e: + print(f"Error in main: {e}") -# Print name of current active document. -print(app.doJavaScript("app.activeDocument.name")) +if __name__ == "__main__": + main() diff --git a/examples/export_document.py b/examples/export_document.py index 4b975951..a133a106 100644 --- a/examples/export_document.py +++ b/examples/export_document.py @@ -1,22 +1,189 @@ -# Import built-in modules +"""Example of exporting Photoshop documents in various formats. + +This script demonstrates how to: +1. Export documents in different formats (PNG, JPEG, etc.) +2. Configure export options for each format +3. Handle export errors and exceptions +4. Export specific layers or layer groups +""" + +from __future__ import annotations + import os +from enum import Enum from tempfile import mkdtemp +from typing import Optional -# Import third-party modules -import examples._psd_files as psd # Import from examples. +import examples._psd_files as psd # Import from examples -# Import local modules from photoshop import Session +class ExportFormat(Enum): + """Supported export formats.""" + PNG = "png" + JPEG = "jpg" + GIF = "gif" -PSD_FILE = psd.get_psd_files() +def export_document(ps: Session, output_path: str, format: ExportFormat, + layer_name: Optional[str] = None) -> bool: + """Export the active document or a specific layer. + + Args: + ps: Photoshop session instance + output_path: Path to save the exported file + format: Export format + layer_name: Name of the layer to export (optional) + + Returns: + bool: True if export was successful, False otherwise + """ + try: + doc = ps.app.activeDocument + + # If layer name is specified, make it visible and hide others + if layer_name: + js_code = f""" + try {{ + var doc = app.activeDocument; + // Hide all layers + for (var i = 0; i < doc.layers.length; i++) {{ + doc.layers[i].visible = false; + }} + // Show target layer + var found = false; + for (var i = 0; i < doc.layers.length; i++) {{ + if (doc.layers[i].name === "{layer_name}") {{ + doc.layers[i].visible = true; + found = true; + break; + }} + }} + found ? "success" : "Layer not found" + }} catch(e) {{ + e.message + }} + """ + result = ps.app.doJavaScript(js_code) + if result != "success": + print(f"Error preparing layer: {result}") + return False + + # Export document using JavaScript + output_path_js = output_path.replace("\\", "/") + js_code = f""" + try {{ + var doc = app.activeDocument; + var file = new File("{output_path_js}"); + + // Configure export options + var opts = new ExportOptionsSaveForWeb(); + if ("{format.value}" === "png") {{ + opts.format = SaveDocumentType.PNG; + opts.PNG8 = false; + opts.transparency = true; + opts.interlaced = false; + opts.quality = 100; + }} else if ("{format.value}" === "jpg") {{ + opts.format = SaveDocumentType.JPEG; + opts.quality = 90; + opts.optimized = true; + opts.progressive = true; + }} else if ("{format.value}" === "gif") {{ + opts.format = SaveDocumentType.COMPUSERVEGIF; + opts.transparency = true; + opts.dither = Dither.DIFFUSION; + opts.ditherAmount = 75; + opts.lossy = 0; + }} + + // Export document + doc.exportDocument(file, ExportType.SAVEFORWEB, opts); + "success" + }} catch(e) {{ + e.message + }} + """ + result = ps.app.doJavaScript(js_code) + if result != "success": + print(f"Error during export: {result}") + return False + + print(f"Successfully exported to: {output_path}") + return True + + except Exception as e: + print(f"Error exporting document: {e}") + return False -if __name__ == "__main__": - psd_file = PSD_FILE["export_layers_as_png.psd"] +def get_document_layers(ps: Session) -> list[str]: + """Get names of all layers in the active document. + + Args: + ps: Photoshop session instance + + Returns: + list[str]: List of layer names + """ + js_code = """ + try { + var doc = app.activeDocument; + var layers = []; + for (var i = 0; i < doc.layers.length; i++) { + layers.push(doc.layers[i].name); + } + layers.join("\\n") + } catch(e) { + e.message + } + """ + result = ps.app.doJavaScript(js_code) + if isinstance(result, str) and not result.startswith("Error"): + return result.split("\n") + return [] + +def main() -> None: + """Export a Photoshop document in various formats. + + This function demonstrates how to: + 1. Open a PSD file + 2. List available layers + 3. Export in different formats + 4. Export specific layers + """ + # Get sample PSD file + psd_file = psd.get_psd_files()["export_layers_as_png.psd"] + + # Create output directory + temp_dir = os.path.join(mkdtemp(), "photoshop_exports") + os.makedirs(temp_dir, exist_ok=True) + with Session(psd_file, action="open", auto_close=True) as ps: - opts = ps.ExportOptionsSaveForWeb() + try: + # List available layers + layers = get_document_layers(ps) + if layers: + print("\nAvailable layers:") + for layer in layers: + print(f"- {layer}") + + # Export in different formats + for format in ExportFormat: + output_path = os.path.join(temp_dir, f"export.{format.value}") + if export_document(ps, output_path, format): + print(f"Exported full document as {format.name}") + + # Export specific layers as PNG + for layer in layers: + output_path = os.path.join(temp_dir, f"layer_{layer}.png") + if export_document(ps, output_path, ExportFormat.PNG, layer): + print(f"Exported layer: {layer}") + + # Open output directory + print(f"\nExports saved to: {temp_dir}") + os.startfile(temp_dir) + + except Exception as e: + print(f"Error in main: {e}") - png_file = os.path.join(mkdtemp(), "test.png") - active_document = ps.app.activeDocument - active_document.exportDocument(png_file, ps.ExportType.SaveForWeb, opts) - os.startfile(png_file) +if __name__ == "__main__": + main() diff --git a/examples/export_layers_as_png.py b/examples/export_layers_as_png.py index 3f2b730f..0db3e16c 100644 --- a/examples/export_layers_as_png.py +++ b/examples/export_layers_as_png.py @@ -1,41 +1,216 @@ -"""Export every layer as a .png file.""" -# Import built-in modules -import os - -# Import third-party modules -import examples._psd_files as psd # Import from examples. - -# Import local modules -from photoshop import Session +"""Export layers from a Photoshop document as PNG files. +This script demonstrates how to: +1. Export individual layers as PNG files +2. Configure PNG export options +3. Handle layer visibility and selection +4. Process layer groups and nested layers +5. Handle export errors and exceptions +""" -PSD_FILE = psd.get_psd_files() +from __future__ import annotations +import os +from tempfile import mkdtemp +from typing import Any -def hide_all_layers(layers): - for layer in layers: - layer.visible = False +import examples._psd_files as psd # Import from examples +from photoshop import Session -def main(): - psd_file = PSD_FILE["export_layers_as_png.psd"] - with Session(psd_file, action="open") as ps: - doc = ps.active_document - options = ps.PNGSaveOptions() - options.compression = 1 - layers = doc.artLayers +class LayerExporter: + """Class for exporting Photoshop layers.""" + + def __init__(self, ps: Session, output_dir: str) -> None: + """Initialize the layer exporter. + + Args: + ps: Photoshop session instance + output_dir: Directory to save exported files + """ + self.ps = ps + self.doc = ps.active_document + self.output_dir = output_dir + self.export_count = 0 + self.error_count = 0 + + def configure_png_options(self) -> Any: + """Configure PNG export options. + + Returns: + PNG save options object + """ + options = self.ps.PNGSaveOptions() + options.compression = 1 # 0-9 (0=none, 9=maximum) + options.interlaced = False + return options + + def hide_all_layers(self) -> None: + """Hide all layers in the document.""" + js_code = """ + try { + var doc = app.activeDocument; + var hideLayer = function(layer) { + layer.visible = false; + if (layer.layers) { + for (var i = 0; i < layer.layers.length; i++) { + hideLayer(layer.layers[i]); + } + } + } + for (var i = 0; i < doc.layers.length; i++) { + hideLayer(doc.layers[i]); + } + "success" + } catch(e) { + e.message + } + """ + result = self.ps.app.doJavaScript(js_code) + if result != "success": + print(f"Warning: Error hiding layers: {result}") + + def show_layer(self, layer_name: str) -> bool: + """Make a specific layer visible. + + Args: + layer_name: Name of the layer to show + + Returns: + bool: True if layer was found and made visible + """ + js_code = f""" + try {{ + var doc = app.activeDocument; + var findLayer = function(container, name) {{ + for (var i = 0; i < container.layers.length; i++) {{ + var layer = container.layers[i]; + if (layer.name === name) {{ + layer.visible = true; + return true; + }} + if (layer.layers && findLayer(layer, name)) {{ + return true; + }} + }} + return false; + }} + findLayer(doc, "{layer_name}") ? "success" : "Layer not found" + }} catch(e) {{ + e.message + }} + """ + result = self.ps.app.doJavaScript(js_code) + return result == "success" + + def get_all_layers(self) -> list[str]: + """Get names of all layers in the document. + + Returns: + list[str]: List of layer names + """ + js_code = """ + try { + var doc = app.activeDocument; + var layers = []; + var getLayers = function(container) { + for (var i = 0; i < container.layers.length; i++) { + var layer = container.layers[i]; + layers.push(layer.name); + if (layer.layers) { + getLayers(layer); + } + } + } + getLayers(doc); + layers.join("\\n") + } catch(e) { + e.message + } + """ + result = self.ps.app.doJavaScript(js_code) + if isinstance(result, str) and not result.startswith("Error"): + return result.split("\n") + return [] + + def export_layer(self, layer_name: str) -> bool: + """Export a single layer as PNG. + + Args: + layer_name: Name of the layer to export + + Returns: + bool: True if export was successful + """ + try: + # Hide all layers and show target layer + self.hide_all_layers() + if not self.show_layer(layer_name): + print(f"Error: Layer '{layer_name}' not found") + self.error_count += 1 + return False + + # Create layer directory + layer_dir = os.path.join(self.output_dir, layer_name) + os.makedirs(layer_dir, exist_ok=True) + + # Configure export options + options = self.configure_png_options() + + # Export layer + image_path = os.path.join(layer_dir, f"{layer_name}.png") + image_path = image_path.replace("\\", "/") # Fix path for Photoshop + self.doc.saveAs(image_path, options=options, asCopy=True) + + print(f"Successfully exported: {image_path}") + self.export_count += 1 + return True + + except Exception as e: + print(f"Error exporting layer '{layer_name}': {e}") + self.error_count += 1 + return False + + def export_all_layers(self) -> None: + """Export all layers as PNG files.""" + layers = self.get_all_layers() + if not layers: + print("No layers found in document") + return + + print(f"\nFound {len(layers)} layers:") + for layer in layers: + print(f"- {layer}") + + print("\nExporting layers...") for layer in layers: - hide_all_layers(layers) - layer.visible = True - layer_path = os.path.join(doc.path, layer.name) - print(layer_path) - if not os.path.exists(layer_path): - os.makedirs(layer_path) - image_path = os.path.join(layer_path, f"{layer.name}.png") - doc.saveAs(image_path, options=options, asCopy=True) - ps.alert("Task done!") - ps.echo(doc.activeLayer) + self.export_layer(layer) + + print("\nExport complete!") + print(f"Successfully exported: {self.export_count} layers") + print(f"Failed to export: {self.error_count} layers") + print(f"Output directory: {self.output_dir}") +def main() -> None: + """Export layers from a Photoshop document as PNG files.""" + # Get sample PSD file + psd_file = psd.get_psd_files()["export_layers_as_png.psd"] + + # Create output directory + temp_dir = os.path.join(mkdtemp(), "photoshop_layers") + os.makedirs(temp_dir, exist_ok=True) + + with Session(psd_file, action="open", auto_close=True) as ps: + try: + # Create exporter and export layers + exporter = LayerExporter(ps, temp_dir) + exporter.export_all_layers() + + # Open output directory + os.startfile(temp_dir) + + except Exception as e: + print(f"Error in main: {e}") if __name__ == "__main__": main() diff --git a/examples/export_layers_use_export_options_saveforweb.py b/examples/export_layers_use_export_options_saveforweb.py index aa84ecc2..333e8b49 100644 --- a/examples/export_layers_use_export_options_saveforweb.py +++ b/examples/export_layers_use_export_options_saveforweb.py @@ -1,40 +1,260 @@ -"""Export every layer as a .png file use `ExportOptionsSaveForWeb`.""" -# Import built-in modules -import os - -# Import third-party modules -import examples._psd_files as psd # Import from examples. +"""Export layers from a Photoshop document using SaveForWeb options. -# Import local modules -from photoshop import Session +This script demonstrates how to: +1. Export layers using SaveForWeb options +2. Configure various export formats and settings +3. Handle layer visibility and selection +4. Process layer groups and nested layers +5. Handle export errors and exceptions +""" +from __future__ import annotations -PSD_FILE = psd.get_psd_files() +import os +from enum import Enum +from tempfile import mkdtemp +import examples._psd_files as psd # Import from examples -def hide_all_layers(layers): - for layer in layers: - layer.visible = False +from photoshop import Session +class ExportFormat(Enum): + """Supported export formats.""" + PNG = "png" + JPEG = "jpg" + GIF = "gif" -def main(): - psd_file = PSD_FILE["export_layers_as_png.psd"] - with Session(psd_file, action="open") as ps: - doc = ps.active_document - options = ps.ExportOptionsSaveForWeb() - layers = doc.artLayers +class WebExporter: + """Class for exporting Photoshop layers using SaveForWeb options.""" + + def __init__(self, ps: Session, output_dir: str) -> None: + """Initialize the web exporter. + + Args: + ps: Photoshop session instance + output_dir: Directory to save exported files + """ + self.ps = ps + self.doc = ps.active_document + self.output_dir = output_dir + self.export_count = 0 + self.error_count = 0 + + def hide_all_layers(self) -> None: + """Hide all layers in the document.""" + js_code = """ + try { + var doc = app.activeDocument; + var hideLayer = function(layer) { + layer.visible = false; + if (layer.layers) { + for (var i = 0; i < layer.layers.length; i++) { + hideLayer(layer.layers[i]); + } + } + } + for (var i = 0; i < doc.layers.length; i++) { + hideLayer(doc.layers[i]); + } + "success" + } catch(e) { + e.message + } + """ + result = self.ps.app.doJavaScript(js_code) + if result != "success": + print(f"Warning: Error hiding layers: {result}") + + def show_layer(self, layer_name: str) -> bool: + """Make a specific layer visible. + + Args: + layer_name: Name of the layer to show + + Returns: + bool: True if layer was found and made visible + """ + js_code = f""" + try {{ + var doc = app.activeDocument; + var findLayer = function(container, name) {{ + for (var i = 0; i < container.layers.length; i++) {{ + var layer = container.layers[i]; + if (layer.name === name) {{ + layer.visible = true; + return true; + }} + if (layer.layers && findLayer(layer, name)) {{ + return true; + }} + }} + return false; + }} + findLayer(doc, "{layer_name}") ? "success" : "Layer not found" + }} catch(e) {{ + e.message + }} + """ + result = self.ps.app.doJavaScript(js_code) + return result == "success" + + def get_all_layers(self) -> list[str]: + """Get names of all layers in the document. + + Returns: + list[str]: List of layer names + """ + js_code = """ + try { + var doc = app.activeDocument; + var layers = []; + var getLayers = function(container) { + for (var i = 0; i < container.layers.length; i++) { + var layer = container.layers[i]; + layers.push(layer.name); + if (layer.layers) { + getLayers(layer); + } + } + } + getLayers(doc); + layers.join("\\n") + } catch(e) { + e.message + } + """ + result = self.ps.app.doJavaScript(js_code) + if isinstance(result, str) and not result.startswith("Error"): + return result.split("\n") + return [] + + def export_layer(self, layer_name: str, format: ExportFormat) -> bool: + """Export a single layer using SaveForWeb options. + + Args: + layer_name: Name of the layer to export + format: Export format + + Returns: + bool: True if export was successful + """ + try: + # Hide all layers and show target layer + self.hide_all_layers() + if not self.show_layer(layer_name): + print(f"Error: Layer '{layer_name}' not found") + self.error_count += 1 + return False + + # Create layer directory + layer_dir = os.path.join(self.output_dir, layer_name) + os.makedirs(layer_dir, exist_ok=True) + + # Export layer using JavaScript + output_path = os.path.join(layer_dir, f"{layer_name}.{format.value}") + output_path_js = output_path.replace("\\", "/") + + js_code = f""" + try {{ + var doc = app.activeDocument; + var file = new File("{output_path_js}"); + + // Configure export options + var opts = new ExportOptionsSaveForWeb(); + if ("{format.value}" === "png") {{ + opts.format = SaveDocumentType.PNG; + opts.PNG8 = false; // Use PNG-24 + opts.transparency = true; + opts.interlaced = false; + opts.quality = 100; + }} else if ("{format.value}" === "jpg") {{ + opts.format = SaveDocumentType.JPEG; + opts.quality = 90; + opts.optimized = true; + opts.progressive = true; + }} else if ("{format.value}" === "gif") {{ + opts.format = SaveDocumentType.COMPUSERVEGIF; + opts.transparency = true; + opts.dither = Dither.DIFFUSION; + opts.ditherAmount = 75; + opts.lossy = 0; + opts.colors = 256; + }} + + // Export document + doc.exportDocument(file, ExportType.SAVEFORWEB, opts); + "success" + }} catch(e) {{ + e.message + }} + """ + result = self.ps.app.doJavaScript(js_code) + if result != "success": + print(f"Error during export: {result}") + self.error_count += 1 + return False + + print(f"Successfully exported: {output_path}") + self.export_count += 1 + return True + + except Exception as e: + print(f"Error exporting layer '{layer_name}': {e}") + self.error_count += 1 + return False + + def export_all_layers(self, formats: list[ExportFormat]) -> None: + """Export all layers in specified formats. + + Args: + formats: List of formats to export in + """ + layers = self.get_all_layers() + if not layers: + print("No layers found in document") + return + + print(f"\nFound {len(layers)} layers:") + for layer in layers: + print(f"- {layer}") + + print("\nExporting layers...") for layer in layers: - hide_all_layers(layers) - layer.visible = True - layer_path = os.path.join(doc.path, layer.name) - print(layer_path) - if not os.path.exists(layer_path): - os.makedirs(layer_path) - 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) + for format in formats: + self.export_layer(layer, format) + + print("\nExport complete!") + print(f"Successfully exported: {self.export_count} files") + print(f"Failed to export: {self.error_count} files") + print(f"Output directory: {self.output_dir}") +def main() -> None: + """Export layers from a Photoshop document using SaveForWeb options.""" + # Get sample PSD file + psd_file = psd.get_psd_files()["export_layers_as_png.psd"] + + # Create output directory + temp_dir = os.path.join(mkdtemp(), "photoshop_exports") + os.makedirs(temp_dir, exist_ok=True) + + # Define export formats + formats = [ + ExportFormat.PNG, + ExportFormat.JPEG, + ExportFormat.GIF, + ] + + with Session(psd_file, action="open", auto_close=True) as ps: + try: + # Create exporter and export layers + exporter = WebExporter(ps, temp_dir) + exporter.export_all_layers(formats) + + # Open output directory + os.startfile(temp_dir) + + except Exception as e: + print(f"Error in main: {e}") if __name__ == "__main__": main() diff --git a/examples/fill_selection.py b/examples/fill_selection.py index 25111f0c..f2915a51 100644 --- a/examples/fill_selection.py +++ b/examples/fill_selection.py @@ -1,26 +1,358 @@ -# Fill the current selection with an RGB color. +"""Fill selections in a Photoshop document with various colors and blend modes. +This script demonstrates how to: +1. Create and manage document selections +2. Fill selections with colors +3. Use different blend modes +4. Handle document and layer states +5. Handle errors and exceptions +""" + +from __future__ import annotations + +from dataclasses import dataclass +from enum import Enum +from typing import Any, Optional, Tuple, Union -# Import local modules from photoshop import Session +class ColorPreset(Enum): + """Predefined color presets.""" + RED = (255, 0, 0) + GREEN = (0, 255, 0) + BLUE = (0, 0, 255) + YELLOW = (255, 255, 0) + CYAN = (0, 255, 255) + MAGENTA = (255, 0, 255) + BLACK = (0, 0, 0) + WHITE = (255, 255, 255) + +@dataclass +class DocumentSettings: + """Settings for creating new documents.""" + width: int = 800 + height: int = 600 + resolution: float = 72.0 + name: Optional[str] = None + mode: Optional[str] = None + initial_fill: Optional[str] = None + color_profile: Optional[str] = None + bit_depth: Optional[int] = None + pixel_aspect: Optional[float] = None + +class SelectionFiller: + """Class for filling selections in Photoshop documents.""" + + def __init__(self, ps: Session) -> None: + """Initialize the selection filler. + + Args: + ps: Photoshop session instance + """ + self.ps = ps + self._original_ruler_units = ps.app.preferences.rulerUnits + self._layer_count = 0 + + def _set_ruler_units(self, units: Any) -> None: + """Set ruler units temporarily. + + Args: + units: Ruler units to set + """ + self.ps.app.preferences.rulerUnits = units + + def _restore_ruler_units(self) -> None: + """Restore original ruler units.""" + self.ps.app.preferences.rulerUnits = self._original_ruler_units + + def create_new_layer(self, name: Optional[str] = None) -> bool: + """Create a new layer. + + Args: + name: Name for the new layer + + Returns: + bool: True if layer was created successfully + """ + try: + # Create layer name if not provided + if not name: + self._layer_count += 1 + name = f"Fill Layer {self._layer_count}" + + # Create layer using JavaScript + js_code = f""" + try {{ + var doc = app.activeDocument; + var layer = doc.artLayers.add(); + layer.name = "{name}"; + "success" + }} catch(e) {{ + e.message + }} + """ + result = self.ps.app.doJavaScript(js_code) + + if result == "success": + print(f"Created new layer: {name}") + return True + print(f"Error creating layer: {result}") + return False + + except Exception as e: + print(f"Error creating layer: {e}") + return False + + def ensure_document_exists(self, settings: Optional[DocumentSettings] = None) -> None: + """Ensure a document exists, create one if needed. + + Args: + settings: Document settings to use when creating new document + """ + if len(self.ps.app.documents) < 1: + if not settings: + settings = DocumentSettings() + + # Set units to pixels temporarily + original_units = self.ps.app.preferences.rulerUnits + self._set_ruler_units(self.ps.Units.Pixels) + + try: + # Create new document + doc = self.ps.app.documents.add( + settings.width, + settings.height, + settings.resolution, + settings.name, + self.ps.NewDocumentMode.NewRGB, + self.ps.DocumentFill.White, + ) + + # Create initial selection + self.create_initial_selection() + + finally: + # Restore original units + self._restore_ruler_units() + + def create_initial_selection(self) -> None: + """Create an initial rectangular selection in the document.""" + try: + doc = self.ps.active_document + + # Get document dimensions + width = doc.width + height = doc.height + + # Create selection array (25% margin from edges) + left = int(width * 0.25) + top = int(height * 0.25) + right = int(width * 0.75) + bottom = int(height * 0.75) + + # Create selection using JavaScript + js_code = f""" + try {{ + var doc = app.activeDocument; + var region = [ + [{left}, {top}], + [{right}, {top}], + [{right}, {bottom}], + [{left}, {bottom}] + ]; + doc.selection.select(region); + "success" + }} catch(e) {{ + e.message + }} + """ + result = self.ps.app.doJavaScript(js_code) + + if result == "success": + print(f"Created selection: ({left}, {top}, {right}, {bottom})") + else: + print(f"Warning: Error creating selection: {result}") + + except Exception as e: + print(f"Warning: Error creating initial selection: {e}") + + def create_solid_color(self, color: Union[ColorPreset, Tuple[int, int, int]]) -> Any: + """Create a solid color object. + + Args: + color: Color preset or RGB tuple + + Returns: + SolidColor object + """ + if isinstance(color, ColorPreset): + rgb = color.value + else: + rgb = color + + fill_color = self.ps.SolidColor() + fill_color.rgb.red = rgb[0] + fill_color.rgb.green = rgb[1] + fill_color.rgb.blue = rgb[2] + return fill_color + + def ensure_selection_exists(self) -> bool: + """Ensure a selection exists, create one if needed. + + Returns: + bool: True if selection exists or was created + """ + try: + # Check selection using JavaScript + js_code = """ + try { + var doc = app.activeDocument; + var bounds = doc.selection.bounds; + bounds[0] + "," + bounds[1] + "," + bounds[2] + "," + bounds[3] + } catch(e) { + "no_selection" + } + """ + result = self.ps.app.doJavaScript(js_code) + + if result == "no_selection": + print("No existing selection found, creating new one...") + self.create_initial_selection() + return True + print(f"Found existing selection: {result}") + return True + + except Exception as e: + print(f"Error checking selection: {e}") + return False + + def fill_selection(self, + color: Union[ColorPreset, Tuple[int, int, int]], + blend_mode: Optional[Any] = None, + opacity: float = 100.0, + preserve_transparency: bool = False) -> bool: + """Fill the current selection with a color. + + Args: + color: Color to fill with (preset or RGB tuple) + blend_mode: Blend mode to use + opacity: Fill opacity (0-100) + preserve_transparency: Whether to preserve transparency + + Returns: + bool: True if fill was successful + """ + try: + doc = self.ps.active_document + + # Create new layer for fill + if not self.create_new_layer(): + print("Error: Could not create new layer") + return False + + # Ensure we have a selection + if not self.ensure_selection_exists(): + print("Error: Could not create or find selection") + return False + + # Create fill color + fill_color = self.create_solid_color(color) + + # Fill selection using JavaScript + js_code = f""" + try {{ + var doc = app.activeDocument; + var color = new SolidColor(); + color.rgb.red = {fill_color.rgb.red}; + color.rgb.green = {fill_color.rgb.green}; + color.rgb.blue = {fill_color.rgb.blue}; + + // Create action for fill + var idFl = charIDToTypeID("Fl "); + var desc = new ActionDescriptor(); + var idUsng = charIDToTypeID("Usng"); + var idFlCn = charIDToTypeID("FlCn"); + var idClr = charIDToTypeID("Clr "); + desc.putEnumerated(idUsng, idFlCn, idClr); + + var idClr = charIDToTypeID("Clr "); + var desc2 = new ActionDescriptor(); + var idRd = charIDToTypeID("Rd "); + var idPrct = charIDToTypeID("#Prc"); + desc2.putDouble(idRd, {fill_color.rgb.red}); + var idGrn = charIDToTypeID("Grn "); + desc2.putDouble(idGrn, {fill_color.rgb.green}); + var idBl = charIDToTypeID("Bl "); + desc2.putDouble(idBl, {fill_color.rgb.blue}); + var idRGBC = charIDToTypeID("RGBC"); + desc.putObject(idClr, idRGBC, desc2); + + var idOpct = charIDToTypeID("Opct"); + var idPrc = charIDToTypeID("#Prc"); + desc.putUnitDouble(idOpct, idPrc, {opacity}); + + var idMd = charIDToTypeID("Md "); + var idBlnM = charIDToTypeID("BlnM"); + var idNrml = charIDToTypeID("Nrml"); + desc.putEnumerated(idMd, idBlnM, idNrml); + + executeAction(idFl, desc, DialogModes.NO); + "success" + }} catch(e) {{ + e.message + }} + """ + result = self.ps.app.doJavaScript(js_code) + + if result == "success": + print(f"Successfully filled selection with color RGB({fill_color.rgb.red}, {fill_color.rgb.green}, {fill_color.rgb.blue})") + return True + print(f"Error during fill operation: {result}") + return False + + except Exception as e: + print(f"Error filling selection: {e}") + return False + + def fill_with_preset(self, + preset: ColorPreset, + blend_mode: Optional[Any] = None, + opacity: float = 100.0) -> bool: + """Fill the current selection with a preset color. + + Args: + preset: Color preset to use + blend_mode: Blend mode to use + opacity: Fill opacity (0-100) + + Returns: + bool: True if fill was successful + """ + return self.fill_selection(preset, blend_mode, opacity) + +def main() -> None: + """Fill selections in a Photoshop document.""" + with Session() as ps: + try: + # Create filler and ensure document exists + print("\nInitializing...") + filler = SelectionFiller(ps) + filler.ensure_document_exists() + + # Fill selection with different colors and blend modes + print("\nFilling with preset colors...") + filler.fill_with_preset(ColorPreset.RED, ps.ColorBlendMode.NormalBlendColor, 50) + + print("\nFilling with custom colors...") + custom_color = (128, 64, 255) # Purple + filler.fill_selection( + custom_color, + ps.ColorBlendMode.MultiplyBlend, + 75, + ) + + except Exception as e: + print(f"Error in main: {e}") -with Session() as ps: - start_ruler_units = ps.app.Preferences.RulerUnits - - if len(ps.app.documents) < 1: - if start_ruler_units is not ps.Units.Pixels: - ps.app.Preferences.RulerUnits = ps.Units.Pixels - docRef = ps.app.documents.add(320, 240, 72, None, ps.NewDocumentMode.NewRGB, ps.DocumentFill.White) - docRef.artLayers.add() - ps.app.preferences.rulerUnits = start_ruler_units - - if not ps.active_document.activeLayer.isBackgroundLayer: - selRef = ps.active_document.selection - fillcolor = ps.SolidColor() - fillcolor.rgb.red = 225 - fillcolor.rgb.green = 0 - fillcolor.rgb.blue = 0 - selRef.fill(fillcolor, ps.ColorBlendMode.NormalBlendColor, 25, False) - else: - ps.echo("Can't perform operation on background layer.") +if __name__ == "__main__": + main() diff --git a/examples/fit_on_screen.py b/examples/fit_on_screen.py index 8f6401be..4956b635 100644 --- a/examples/fit_on_screen.py +++ b/examples/fit_on_screen.py @@ -1,8 +1,166 @@ -"""Let the current document Fit on screen.""" +"""Manage document view and zoom in Photoshop. + +This script demonstrates how to: +1. Fit document on screen +2. View at actual pixels +3. Zoom in and out +4. Handle different view modes +""" + +from __future__ import annotations + +import enum -# Import local modules from photoshop import Session +class ViewMode(enum.Enum): + """View modes for document display.""" + FIT_ON_SCREEN = "FtOn" + ACTUAL_PIXELS = "ActP" + PRINT_SIZE = "PrnS" + ZOOM_IN = "ZmIn" + ZOOM_OUT = "ZmOt" + +class ViewManager: + """Class for managing document view and zoom.""" + + def __init__(self, ps: Session) -> None: + """Initialize the view manager. + + Args: + ps: Photoshop session instance + """ + self.ps = ps + + def ensure_document_exists(self) -> bool: + """Ensure a document exists. + + Returns: + bool: True if document exists + """ + try: + if len(self.ps.app.documents) < 1: + print("Error: No document is open") + return False + return True + except Exception as e: + print(f"Error checking document: {e}") + return False + + def set_view_mode(self, mode: ViewMode) -> bool: + """Set the view mode for the current document. + + Args: + mode: View mode to set + + Returns: + bool: True if view mode was set successfully + """ + try: + if not self.ensure_document_exists(): + return False + + # Set view mode using runMenuItem + js_code = f""" + try {{ + app.runMenuItem(app.charIDToTypeID("{mode.value}")); + "success" + }} catch(e) {{ + e.message + }} + """ + result = self.ps.app.doJavaScript(js_code) + + if result == "success": + print(f"Set view mode to: {mode.name}") + return True + print(f"Error setting view mode: {result}") + return False + + except Exception as e: + print(f"Error setting view mode: {e}") + return False + + def zoom_in(self, steps: int = 1) -> bool: + """Zoom in by a number of steps. + + Args: + steps: Number of zoom in steps + + Returns: + bool: True if zoom was successful + """ + try: + for _ in range(steps): + if not self.set_view_mode(ViewMode.ZOOM_IN): + return False + return True + except Exception as e: + print(f"Error zooming in: {e}") + return False + + def zoom_out(self, steps: int = 1) -> bool: + """Zoom out by a number of steps. + + Args: + steps: Number of zoom out steps + + Returns: + bool: True if zoom was successful + """ + try: + for _ in range(steps): + if not self.set_view_mode(ViewMode.ZOOM_OUT): + return False + return True + except Exception as e: + print(f"Error zooming out: {e}") + return False + + def fit_on_screen(self) -> bool: + """Fit the document on screen. + + Returns: + bool: True if successful + """ + return self.set_view_mode(ViewMode.FIT_ON_SCREEN) + + def actual_pixels(self) -> bool: + """View document at actual pixels. + + Returns: + bool: True if successful + """ + return self.set_view_mode(ViewMode.ACTUAL_PIXELS) + +def main() -> None: + """Demonstrate document view management.""" + with Session() as ps: + try: + # Create view manager + print("\nInitializing view manager...") + manager = ViewManager(ps) + + if not manager.ensure_document_exists(): + print("Please open a document first") + return + + # Try different view modes + print("\nTesting view modes...") + manager.fit_on_screen() + manager.actual_pixels() + + # Test zoom in/out + print("\nTesting zoom in/out...") + manager.zoom_in(2) + manager.zoom_out(1) + + # Return to fit on screen + print("\nReturning to fit on screen...") + manager.fit_on_screen() + + except Exception as e: + print(f"Error in main: {e}") -with Session() as ps: - ps.app.runMenuItem(ps.app.charIDToTypeID("FtOn")) +if __name__ == "__main__": + main() diff --git a/examples/get_document_by_name.py b/examples/get_document_by_name.py index 7b9d04fc..4d884c0b 100644 --- a/examples/get_document_by_name.py +++ b/examples/get_document_by_name.py @@ -1,15 +1,181 @@ -"""Get document by document name from documents.""" +"""Manage and retrieve Photoshop documents by name. -# Import third-party modules -import examples._psd_files as psd # Import from examples. +This script demonstrates how to: +1. Get document by name +2. List all open documents +3. Get document properties +4. Handle document errors +""" -# Import local modules +from __future__ import annotations + +import os +from dataclasses import dataclass +from typing import Any, List, Optional + +import examples._psd_files as psd # Import from examples from photoshop import Session +@dataclass +class DocumentInfo: + """Information about a Photoshop document.""" + name: str + path: str + width: float + height: float + resolution: float + mode: str + bits_per_channel: int + channels: int + layers: int + +class DocumentManager: + """Class for managing Photoshop documents.""" + + def __init__(self, ps: Session) -> None: + """Initialize the document manager. + + Args: + ps: Photoshop session instance + """ + self.ps = ps + + def list_documents(self) -> List[str]: + """List all open documents. + + Returns: + List of document names + """ + try: + docs = [doc.name for doc in self.ps.app.documents] + if docs: + print("\nOpen documents:") + for name in docs: + print(f"- {name}") + else: + print("No documents are open") + return docs + + except Exception as e: + print(f"Error listing documents: {e}") + return [] + + def get_document_by_name(self, name: str) -> Optional[Any]: + """Get document by name. + + Args: + name: Document name to find + + Returns: + Document object if found, None otherwise + """ + try: + doc = self.ps.app.documents.getByName(name) + print(f"\nFound document: {doc.name}") + print(f"Full path: {doc.fullName}") + return doc + + except Exception as e: + print(f"Error getting document '{name}': {e}") + return None + + def get_document_info(self, doc: Any) -> Optional[DocumentInfo]: + """Get detailed information about a document. + + Args: + doc: Document object + + Returns: + DocumentInfo object if successful, None otherwise + """ + try: + # Get document dimensions using JavaScript + js_code = """ + try { + var doc = app.activeDocument; + doc.width.value + "," + doc.height.value + } catch(e) { + e.message + } + """ + result = self.ps.app.doJavaScript(js_code) + + try: + width, height = map(float, result.split(",")) + except Exception: + print("Error getting document dimensions") + return None + + # Create document info + info = DocumentInfo( + name=doc.name, + path=str(doc.fullName), + width=width, + height=height, + resolution=float(doc.resolution), + mode=str(doc.mode), + bits_per_channel=int(doc.bitsPerChannel), + channels=len(doc.channels), + layers=len(doc.artLayers), + ) + + print(f"\nDocument information for {info.name}:") + print(f"- Path: {info.path}") + print(f"- Size: {info.width:.1f}x{info.height:.1f} pixels") + print(f"- Resolution: {info.resolution:.1f} PPI") + print(f"- Mode: {info.mode}") + print(f"- Bits per channel: {info.bits_per_channel}") + print(f"- Channels: {info.channels}") + print(f"- Layers: {info.layers}") + + return info + + except Exception as e: + print(f"Error getting document info: {e}") + return None + + def document_exists(self, name: str) -> bool: + """Check if a document exists. + + Args: + name: Document name to check + + Returns: + True if document exists + """ + try: + self.ps.app.documents.getByName(name) + return True + except Exception: + return False + +def main() -> None: + """Demonstrate document management.""" + # Get sample PSD file + PSD_FILE = psd.get_psd_files() + template_path = PSD_FILE["slate_template.psd"] + template_name = os.path.basename(template_path) + + # Open document and manage it + with Session(template_path, action="open", auto_close=True) as ps: + try: + print("\nInitializing document manager...") + manager = DocumentManager(ps) + + # List all documents + manager.list_documents() + + # Get document by name + if manager.document_exists(template_name): + doc = manager.get_document_by_name(template_name) + if doc: + # Get document info + manager.get_document_info(doc) + else: + print(f"\nDocument '{template_name}' not found") + + except Exception as e: + print(f"Error in main: {e}") -PSD_FILE = psd.get_psd_files() -slate_template = PSD_FILE["slate_template.psd"] -with Session(slate_template, action="open", auto_close=True) as ps: - for doc in ps.app.documents: - print(doc.name) - print(ps.app.documents.getByName("slate_template.psd").fullName) +if __name__ == "__main__": + main() diff --git a/examples/get_layer_by_name.py b/examples/get_layer_by_name.py index 2bb618eb..8bd6e38e 100644 --- a/examples/get_layer_by_name.py +++ b/examples/get_layer_by_name.py @@ -1,13 +1,235 @@ -# Import third-party modules -import examples._psd_files as psd # Import from examples. +"""Manage and retrieve Photoshop layers by name. -# Import local modules +This script demonstrates how to: +1. Get layer by name +2. List all layers in document +3. Get layer properties +4. Handle layer errors +""" + +from __future__ import annotations + +from dataclasses import dataclass +from enum import Enum +from typing import Any, List, Optional + +import examples._psd_files as psd # Import from examples from photoshop import Session +class LayerType(Enum): + """Types of Photoshop layers.""" + NORMAL = "normal" + TEXT = "text" + ADJUSTMENT = "adjustment" + SHAPE = "shape" + SMART_OBJECT = "smartObject" + BACKGROUND = "background" + GROUP = "group" + +@dataclass +class LayerInfo: + """Information about a Photoshop layer.""" + name: str + type: LayerType + visible: bool + opacity: float + blend_mode: str + locked: bool + linked: bool + +class LayerManager: + """Class for managing Photoshop layers.""" + + def __init__(self, ps: Session) -> None: + """Initialize the layer manager. + + Args: + ps: Photoshop session instance + """ + self.ps = ps + + def ensure_document_exists(self) -> bool: + """Ensure a document exists. + + Returns: + bool: True if document exists + """ + try: + if len(self.ps.app.documents) < 1: + print("Error: No document is open") + return False + return True + except Exception as e: + print(f"Error checking document: {e}") + return False + + def list_layers(self, include_hidden: bool = True) -> List[str]: + """List all layers in the active document. + + Args: + include_hidden: Whether to include hidden layers + + Returns: + List of layer names + """ + try: + if not self.ensure_document_exists(): + return [] + + doc = self.ps.active_document + layers = [] + + # Get all art layers + for layer in doc.artLayers: + if include_hidden or layer.visible: + layers.append(layer.name) + + if layers: + print("\nLayers in document:") + for name in layers: + print(f"- {name}") + else: + print("No layers found") + + return layers + + except Exception as e: + print(f"Error listing layers: {e}") + return [] + + def get_layer_by_name(self, name: str) -> Optional[Any]: + """Get layer by name. + + Args: + name: Layer name to find + + Returns: + Layer object if found, None otherwise + """ + try: + if not self.ensure_document_exists(): + return None + + layer = self.ps.active_document.artLayers.getByName(name) + print(f"\nFound layer: {layer.name}") + return layer + + except Exception as e: + print(f"Error getting layer '{name}': {e}") + return None + + def get_layer_info(self, layer: Any) -> Optional[LayerInfo]: + """Get detailed information about a layer. + + Args: + layer: Layer object + + Returns: + LayerInfo object if successful, None otherwise + """ + try: + # Get layer properties using JavaScript + js_code = f""" + try {{ + var doc = app.activeDocument; + var layer = doc.artLayers.getByName("{layer.name}"); + var result = layer.visible + "," + layer.opacity + "," + layer.blendMode + "," + layer.allLocked; + result + }} catch(e) {{ + e.message + }} + """ + result = self.ps.app.doJavaScript(js_code) + + try: + # Parse JavaScript result + visible, opacity, blend_mode, locked = result.split(",") + + # Determine layer type + layer_type = LayerType.NORMAL + try: + if layer.kind == self.ps.LayerKind.TextLayer: + layer_type = LayerType.TEXT + elif layer.kind == self.ps.LayerKind.AdjustmentLayer: + layer_type = LayerType.ADJUSTMENT + elif layer.kind == self.ps.LayerKind.SmartObject: + layer_type = LayerType.SMART_OBJECT + elif layer.isBackgroundLayer: + layer_type = LayerType.BACKGROUND + except Exception: + print("Warning: Could not determine layer type") + + # Create layer info + info = LayerInfo( + name=layer.name, + type=layer_type, + visible=visible.lower() == "true", + opacity=float(opacity), + blend_mode=str(blend_mode), + locked=locked.lower() == "true", + linked=False, # Simplified for now + ) + + print(f"\nLayer information for {info.name}:") + print(f"- Type: {info.type.value}") + print(f"- Visible: {info.visible}") + print(f"- Opacity: {info.opacity:.1f}%") + print(f"- Blend Mode: {info.blend_mode}") + print(f"- Locked: {info.locked}") + print(f"- Linked: {info.linked}") + + return info + + except Exception as e: + print(f"Error parsing layer properties: {e}") + return None + + except Exception as e: + print(f"Error getting layer info: {e}") + return None + + def layer_exists(self, name: str) -> bool: + """Check if a layer exists. + + Args: + name: Layer name to check + + Returns: + True if layer exists + """ + try: + self.ps.active_document.artLayers.getByName(name) + return True + except Exception: + return False -PSD_FILE = psd.get_psd_files() +def main() -> None: + """Demonstrate layer management.""" + # Get sample PSD file + PSD_FILE = psd.get_psd_files() + psd_path = PSD_FILE["export_layers_as_png.psd"] + + # Open document and manage layers + with Session(psd_path, action="open", auto_close=True) as ps: + try: + print("\nInitializing layer manager...") + manager = LayerManager(ps) + + # List all layers + manager.list_layers() + + # Get layer by name + layer_name = "blue" + if manager.layer_exists(layer_name): + layer = manager.get_layer_by_name(layer_name) + if layer: + # Get layer info + manager.get_layer_info(layer) + else: + print(f"\nLayer '{layer_name}' not found") + + except Exception as e: + print(f"Error in main: {e}") -psd_file = PSD_FILE["export_layers_as_png.psd"] -with Session(psd_file, action="open", auto_close=True) as ps: - art_layer = ps.active_document.artLayers.getByName("blue") - assert art_layer.name == "blue" +if __name__ == "__main__": + main() diff --git a/examples/hello_world.py b/examples/hello_world.py index c27c48b4..7040f4c9 100644 --- a/examples/hello_world.py +++ b/examples/hello_world.py @@ -1,27 +1,198 @@ -# Import built-in modules +"""Create a Hello World text layer in Photoshop. + +This script demonstrates how to: +1. Create a new document +2. Add a text layer +3. Set text properties +4. Save the document +""" + +from __future__ import annotations + import os +from dataclasses import dataclass +from enum import Enum from tempfile import mkdtemp +from typing import Optional, Tuple, Union -# Import local modules import photoshop.api as ps +class TextAlignment(Enum): + """Text alignment options.""" + LEFT = "left" + CENTER = "center" + RIGHT = "right" + +@dataclass +class TextStyle: + """Text style properties.""" + contents: str + position: Tuple[float, float] + size: float + color: Union[ps.SolidColor, Tuple[int, int, int]] = (0, 0, 0) + font: str = "Arial" + alignment: TextAlignment = TextAlignment.LEFT + bold: bool = False + italic: bool = False + opacity: float = 100.0 -def hello_world(): - app = ps.Application() - doc = app.documents.add() - text_color = ps.SolidColor() - text_color.rgb.green = 255 - new_text_layer = doc.artLayers.add() - new_text_layer.kind = ps.LayerKind.TextLayer - new_text_layer.textItem.contents = "Hello, World!" - new_text_layer.textItem.position = [160, 167] - new_text_layer.textItem.size = 40 - new_text_layer.textItem.color = text_color - options = ps.JPEGSaveOptions(quality=5) - jpg_file = os.path.join(mkdtemp("photoshop-python-api"), "hello_world.jpg") - doc.saveAs(jpg_file, options, asCopy=True) - os.startfile(jpg_file) +class TextCreator: + """Class for creating text layers in Photoshop.""" + + def __init__(self, app: ps.Application) -> None: + """Initialize text creator. + + Args: + app: Photoshop application instance + """ + self.app = app + self.doc = None + self.text_layer = None + + def create_document(self, width: int = 500, height: int = 300) -> bool: + """Create a new document. + + Args: + width: Document width in pixels + height: Document height in pixels + + Returns: + True if successful + """ + try: + self.doc = self.app.documents.add(width=width, height=height) + print(f"\nCreated new document: {width}x{height} pixels") + return True + except Exception as e: + print(f"Error creating document: {e}") + return False + + def create_text_layer(self, style: TextStyle) -> Optional[ps.ArtLayer]: + """Create a text layer with specified style. + + Args: + style: Text style properties + + Returns: + Text layer if successful, None otherwise + """ + try: + if not self.doc: + print("Error: No document is open") + return None + + # Create text layer + self.text_layer = self.doc.artLayers.add() + self.text_layer.kind = ps.LayerKind.TextLayer + + # Set text properties + text_item = self.text_layer.textItem + text_item.contents = style.contents + text_item.position = list(style.position) + text_item.size = style.size + text_item.font = style.font + text_item.justification = { + TextAlignment.LEFT: ps.Justification.Left, + TextAlignment.CENTER: ps.Justification.Center, + TextAlignment.RIGHT: ps.Justification.Right, + }[style.alignment] + + # Set color + if isinstance(style.color, tuple): + color = ps.SolidColor() + color.rgb.red = style.color[0] + color.rgb.green = style.color[1] + color.rgb.blue = style.color[2] + text_item.color = color + else: + text_item.color = style.color + + # Set style + if style.bold: + text_item.fauxBold = True + if style.italic: + text_item.fauxItalic = True + + # Set opacity + self.text_layer.opacity = style.opacity + + print(f"\nCreated text layer: {style.contents}") + print(f"Font: {style.font}, Size: {style.size}pt") + print(f"Position: {style.position}") + print(f"Alignment: {style.alignment.value}") + + return self.text_layer + + except Exception as e: + print(f"Error creating text layer: {e}") + return None + + def save_document(self, file_path: str = None, + quality: int = 5) -> Optional[str]: + """Save document to file. + + Args: + file_path: Path to save file, if None uses temp directory + quality: Save quality (1-100) + + Returns: + File path if successful, None otherwise + """ + try: + if not self.doc: + print("Error: No document is open") + return None + + # Generate file path + if not file_path: + file_path = os.path.join(mkdtemp("photoshop-python-api"), + "hello_world.jpg") + + # Save file + options = ps.JPEGSaveOptions(quality=quality) + self.doc.saveAs(file_path, options, asCopy=True) + + print(f"\nSaved document to: {file_path}") + return file_path + + except Exception as e: + print(f"Error saving document: {e}") + return None +def hello_world() -> None: + """Create a Hello World example.""" + try: + # Initialize Photoshop + app = ps.Application() + creator = TextCreator(app) + + # Create document + if not creator.create_document(500, 300): + return + + # Create text style + style = TextStyle( + contents="Hello, World!", + position=(160, 167), + size=40, + color=(0, 255, 0), # Green color + font="Arial", + alignment=TextAlignment.CENTER, + bold=True, + opacity=90, + ) + + # Create text layer + if not creator.create_text_layer(style): + return + + # Save and open file + file_path = creator.save_document(quality=5) + if file_path: + os.startfile(file_path) + + except Exception as e: + print(f"Error in hello_world: {e}") if __name__ == "__main__": hello_world() diff --git a/examples/import_image_as_layer.py b/examples/import_image_as_layer.py index ed027ee1..a1dd7c7a 100644 --- a/examples/import_image_as_layer.py +++ b/examples/import_image_as_layer.py @@ -1,11 +1,233 @@ -"""Import a image as a artLayer.""" +"""Import images as layers in Photoshop. -# Import local modules +This script demonstrates how to: +1. Import images as layers +2. Set layer properties +3. Adjust layer position and size +4. Handle multiple image formats +""" + +from __future__ import annotations + +from dataclasses import dataclass +from enum import Enum +from pathlib import Path +from typing import List, Optional, Tuple, Union + +import examples._psd_files as psd # Import from examples from photoshop import Session +class ImageFormat(Enum): + """Supported image formats.""" + JPEG = ".jpg" + PNG = ".png" + TIFF = ".tif" + PSD = ".psd" + +@dataclass +class ImportOptions: + """Options for importing images.""" + position: Tuple[float, float] = (0, 0) # x, y coordinates + scale: float = 100.0 # Scale percentage + opacity: float = 100.0 # Layer opacity + blend_mode: str = "normal" # Layer blend mode + linked: bool = False # Link with other layers + +class ImageImporter: + """Class for importing images as layers.""" + + def __init__(self, ps: Session) -> None: + """Initialize image importer. + + Args: + ps: Photoshop session instance + """ + self.ps = ps + self.doc = None + self.imported_layers = [] + + def ensure_document_exists(self) -> bool: + """Ensure a document exists. + + Returns: + bool: True if document exists + """ + try: + if len(self.ps.app.documents) < 1: + print("Error: No document is open") + return False + self.doc = self.ps.active_document + return True + except Exception as e: + print(f"Error checking document: {e}") + return False + + def validate_image_path(self, image_path: Union[str, Path]) -> Optional[Path]: + """Validate image path and format. + + Args: + image_path: Path to image file + + Returns: + Path object if valid, None otherwise + """ + try: + path = Path(image_path).resolve() + if not path.exists(): + print(f"Error: Image not found: {path}") + return None + + ext = path.suffix.lower() + if not any(ext == format.value for format in ImageFormat): + print(f"Error: Unsupported image format: {ext}") + print(f"Supported formats: {[f.value for f in ImageFormat]}") + return None + + return path + + except Exception as e: + print(f"Error validating image path: {e}") + return None + + def import_image(self, image_path: Union[str, Path], + options: ImportOptions = None) -> Optional[ps.ArtLayer]: + """Import image as a layer. + + Args: + image_path: Path to image file + options: Import options + + Returns: + Layer object if successful, None otherwise + """ + try: + # Validate inputs + if not self.ensure_document_exists(): + return None + + path = self.validate_image_path(image_path) + if not path: + return None + + if not options: + options = ImportOptions() + + # Convert path to Photoshop format + ps_path = str(path).replace("\\", "/") + + # Import image using JavaScript + js_code = f""" + try {{ + var idPlc = charIDToTypeID("Plc "); + var desc = new ActionDescriptor(); + desc.putPath(charIDToTypeID("null"), new File("{ps_path}")); + executeAction(idPlc, desc, DialogModes.NO); + + // Get the imported layer + var layer = app.activeDocument.activeLayer; + + // Set layer properties + layer.opacity = {options.opacity}; + layer.blendMode = BlendMode.{options.blend_mode.upper()}; + layer.linked = {str(options.linked).lower()}; + + // Move layer + layer.translate({options.position[0]}, {options.position[1]}); + + // Scale layer if needed + if ({options.scale} != 100) {{ + var scaleX = {options.scale}; + var scaleY = {options.scale}; + layer.resize(scaleX, scaleY, AnchorPosition.MIDDLECENTER); + }} + + "success" + }} catch(e) {{ + e.message + }} + """ + + result = self.ps.app.doJavaScript(js_code) + if result != "success": + print(f"Error importing image: {result}") + return None + + # Get imported layer + layer = self.ps.active_document.activeLayer + self.imported_layers.append(layer) + + print(f"\nImported image: {path.name}") + print(f"Position: {options.position}") + print(f"Scale: {options.scale}%") + print(f"Opacity: {options.opacity}%") + print(f"Blend Mode: {options.blend_mode}") + + return layer + + except Exception as e: + print(f"Error importing image: {e}") + return None + + def import_multiple(self, image_paths: List[Union[str, Path]], + options: ImportOptions = None) -> List[ps.ArtLayer]: + """Import multiple images as layers. + + Args: + image_paths: List of image paths + options: Import options + + Returns: + List of imported layers + """ + layers = [] + for path in image_paths: + layer = self.import_image(path, options) + if layer: + layers.append(layer) + return layers + +def main() -> None: + """Demonstrate image importing.""" + try: + # Get sample image + PSD_FILE = psd.get_psd_files() + image_path = PSD_FILE["layer_comps.psd"] + + print(f"Importing image: {image_path}") + + # Import image + with Session(action="new_document") as ps: + importer = ImageImporter(ps) + + # Create import options + options = ImportOptions( + position=(100, 100), + scale=50, + opacity=80, + blend_mode="normal", # Use normal blend mode for compatibility + linked=False, + ) + + # Import image + layer = importer.import_image(image_path, options) + if not layer: + return + + # Import multiple images + image_paths = [ + PSD_FILE["layer_comps.psd"], + PSD_FILE["slate_template.psd"], + ] + + print("\nImporting multiple images:") + for path in image_paths: + print(f"- {path}") + + layers = importer.import_multiple(image_paths, options) + print(f"\nImported {len(layers)} additional images") + + except Exception as e: + print(f"Error in main: {e}") -with Session(action="new_document") as ps: - desc = ps.ActionDescriptor - desc.putPath(ps.app.charIDToTypeID("null"), "your/image/path") - event_id = ps.app.charIDToTypeID("Plc ") # `Plc` need one space in here. - ps.app.executeAction(ps.app.charIDToTypeID("Plc "), desc) +if __name__ == "__main__": + main() diff --git a/examples/link_layer.py b/examples/link_layer.py index 5a6d11c6..37ce651d 100644 --- a/examples/link_layer.py +++ b/examples/link_layer.py @@ -1,28 +1,295 @@ -# Import local modules -import photoshop.api as ps +"""Link layers in Photoshop. + +This script demonstrates how to: +1. Link multiple layers together +2. Unlink layers +3. Check layer link status +4. Manage layer links with groups +""" +from __future__ import annotations -app = ps.Application() +from dataclasses import dataclass +from typing import List, Optional, Set, Union + +import photoshop.api as ps +from photoshop.api._artlayer import ArtLayer +from photoshop.api._layerSet import LayerSet -start_ruler_units = app.preferences.rulerUnits +@dataclass +class LayerLinkOptions: + """Options for linking layers.""" + preserve_position: bool = True # Keep layer positions when linking + include_effects: bool = True # Include layer effects in linking + include_masks: bool = True # Include layer masks in linking -if len(app.documents) < 1: - if start_ruler_units is not ps.Units.Pixels: - app.preferences.rulerUnits = ps.Units.Pixels - docRef = app.documents.add( - 320, - 240, - 72, - None, - ps.NewDocumentMode.NewRGB, - ps.DocumentFill.BackgroundColor, - ) -else: - docRef = app.activeDocument +class LayerLinker: + """Class for managing layer links.""" + + def __init__(self, app: ps.Application) -> None: + """Initialize layer linker. + + Args: + app: Photoshop application instance + """ + self.app = app + self.doc = None + self.linked_groups: Set[str] = set() # Track linked groups by name + self._save_ruler_units() + + def _save_ruler_units(self) -> None: + """Save current ruler units.""" + self.start_ruler_units = self.app.preferences.rulerUnits + if self.start_ruler_units is not ps.Units.Pixels: + self.app.preferences.rulerUnits = ps.Units.Pixels + + def _restore_ruler_units(self) -> None: + """Restore original ruler units.""" + self.app.preferences.rulerUnits = self.start_ruler_units + + def ensure_document_exists(self) -> bool: + """Ensure a document exists. + + Returns: + bool: True if document exists or was created + """ + try: + if len(self.app.documents) < 1: + # Create new document + self.doc = self.app.documents.add( + width=320, + height=240, + resolution=72, + title=None, + mode=ps.NewDocumentMode.NewRGB, + initialFill=ps.DocumentFill.BackgroundColor, + ) + print("\nCreated new document: 320x240 pixels") + else: + self.doc = self.app.activeDocument + return True + except Exception as e: + print(f"Error ensuring document exists: {e}") + return False + + def get_layer(self, layer: Union[str, ArtLayer, LayerSet]) -> Optional[Union[ArtLayer, LayerSet]]: + """Get layer by name or object. + + Args: + layer: Layer name or object + + Returns: + Layer object if found, None otherwise + """ + try: + if isinstance(layer, str): + # Search in art layers + for art_layer in self.doc.artLayers: + if art_layer.name == layer: + return art_layer + # Search in layer sets + for layer_set in self.doc.layerSets: + if layer_set.name == layer: + return layer_set + print(f"Error: Layer not found: {layer}") + return None + return layer + except Exception as e: + print(f"Error getting layer: {e}") + return None + + def link_layers(self, layers: List[Union[str, ArtLayer, LayerSet]], + options: LayerLinkOptions = None) -> bool: + """Link multiple layers together. + + Args: + layers: List of layer names or objects + options: Link options + + Returns: + bool: True if successful + """ + try: + if not self.ensure_document_exists(): + return False + + if not options: + options = LayerLinkOptions() + + # Get layer objects + layer_objects = [] + for layer in layers: + layer_obj = self.get_layer(layer) + if layer_obj: + layer_objects.append(layer_obj) + + if len(layer_objects) < 2: + print("Error: Need at least 2 layers to link") + return False + + # Link layers + for i in range(1, len(layer_objects)): + try: + # Use JavaScript to link layers with options + js_code = f""" + try {{ + var layer1 = app.activeDocument.layers.getByName("{layer_objects[0].name}"); + var layer2 = app.activeDocument.layers.getByName("{layer_objects[i].name}"); + + // Link layers + layer1.link(layer2); + + // Apply options + layer1.preserveTransparency = {str(options.preserve_position).lower()}; + layer2.preserveTransparency = {str(options.preserve_position).lower()}; + + "success" + }} catch(e) {{ + e.message + }} + """ + + result = self.app.doJavaScript(js_code) + if result != "success": + print(f"Error linking layers: {result}") + return False + + print(f"Linked layers: {layer_objects[0].name} <-> {layer_objects[i].name}") + + except Exception as e: + print(f"Error linking layers: {e}") + return False + + return True + + except Exception as e: + print(f"Error linking layers: {e}") + return False + finally: + self._restore_ruler_units() + + def unlink_layer(self, layer: Union[str, ArtLayer, LayerSet]) -> bool: + """Unlink a layer from all its links. + + Args: + layer: Layer name or object + + Returns: + bool: True if successful + """ + try: + if not self.ensure_document_exists(): + return False + + layer_obj = self.get_layer(layer) + if not layer_obj: + return False + + # Use JavaScript to unlink layer + js_code = f""" + try {{ + var layer = app.activeDocument.layers.getByName("{layer_obj.name}"); + layer.unlink(); + "success" + }} catch(e) {{ + e.message + }} + """ + + result = self.app.doJavaScript(js_code) + if result != "success": + print(f"Error unlinking layer: {result}") + return False + + print(f"Unlinked layer: {layer_obj.name}") + return True + + except Exception as e: + print(f"Error unlinking layer: {e}") + return False + finally: + self._restore_ruler_units() + + def is_linked(self, layer: Union[str, ArtLayer, LayerSet]) -> bool: + """Check if a layer is linked. + + Args: + layer: Layer name or object + + Returns: + bool: True if layer is linked + """ + try: + if not self.ensure_document_exists(): + return False + + layer_obj = self.get_layer(layer) + if not layer_obj: + return False + + # Use JavaScript to check link status + js_code = f""" + try {{ + var layer = app.activeDocument.layers.getByName("{layer_obj.name}"); + layer.linked + }} catch(e) {{ + e.message + }} + """ + + result = self.app.doJavaScript(js_code) + return result == "true" + + except Exception as e: + print(f"Error checking layer link status: {e}") + return False + finally: + self._restore_ruler_units() -layerRef = docRef.artLayers.add() -layerRef2 = docRef.artLayers.add() -layerRef.link(layerRef2) +def main() -> None: + """Demonstrate layer linking.""" + try: + # Initialize Photoshop + app = ps.Application() + linker = LayerLinker(app) + + # Create new document if needed + if not linker.ensure_document_exists(): + return + + # Create test layers + layer1 = linker.doc.artLayers.add() + layer1.name = "Layer 1" + layer2 = linker.doc.artLayers.add() + layer2.name = "Layer 2" + layer3 = linker.doc.artLayers.add() + layer3.name = "Layer 3" + + print("\nCreated test layers:") + print(f"- {layer1.name}") + print(f"- {layer2.name}") + print(f"- {layer3.name}") + + # Create link options + options = LayerLinkOptions( + preserve_position=True, + include_effects=True, + include_masks=True, + ) + + # Link layers + linker.link_layers([layer1, layer2, layer3], options) + + # Check link status + for layer in [layer1, layer2, layer3]: + is_linked = linker.is_linked(layer) + print(f"\n{layer.name} is {'linked' if is_linked else 'not linked'}") + + # Unlink a layer + linker.unlink_layer(layer2) + + except Exception as e: + print(f"Error in main: {e}") -# Set the ruler back to where it was -app.preferences.rulerUnits = start_ruler_units +if __name__ == "__main__": + main() diff --git a/examples/list_documents.py b/examples/list_documents.py index 2d09f4db..a979b054 100644 --- a/examples/list_documents.py +++ b/examples/list_documents.py @@ -1,13 +1,249 @@ -"""List current photoshop all documents.""" +"""List and manage Photoshop documents. + +This script demonstrates how to: +1. List all open documents +2. Get document information +3. Filter documents by properties +4. Sort documents by various criteria +""" + +from __future__ import annotations + +from dataclasses import dataclass +from enum import Enum +from typing import Dict, List, Optional -# Import local modules import photoshop.api as ps +class DocumentSortBy(Enum): + """Document sorting options.""" + NAME = "name" + SIZE = "size" + MODIFIED = "modified" + CREATED = "created" + +@dataclass +class DocumentInfo: + """Document information.""" + name: str + width: int + height: int + resolution: float + mode: str + bit_depth: int + color_profile: str + file_path: Optional[str] = None + modified: bool = False + + @property + def size(self) -> int: + """Get document size in pixels.""" + return self.width * self.height + + def __str__(self) -> str: + """Get string representation.""" + info = [ + f"Name: {self.name}", + f"Size: {self.width}x{self.height} pixels", + f"Resolution: {self.resolution} DPI", + f"Mode: {self.mode}", + f"Bit Depth: {self.bit_depth}", + f"Color Profile: {self.color_profile}", + f"Path: {self.file_path or 'Unsaved'}", + f"Modified: {'Yes' if self.modified else 'No'}", + ] + return "\n".join(info) -app = ps.Application() +class DocumentManager: + """Class for managing Photoshop documents.""" + + def __init__(self, app: ps.Application) -> None: + """Initialize document manager. + + Args: + app: Photoshop application instance + """ + self.app = app + self.cache: Dict[str, DocumentInfo] = {} # Cache document info + + def _get_safe_property(self, obj: any, prop: str, default: any = None) -> any: + """Safely get object property. + + Args: + obj: Object to get property from + prop: Property name + default: Default value if property not found + + Returns: + Property value or default + """ + try: + return getattr(obj, prop) + except Exception: + return default + + def get_document_info(self, doc: ps.Document) -> DocumentInfo: + """Get document information. + + Args: + doc: Photoshop document + + Returns: + Document information + """ + try: + # Get document properties safely + info = DocumentInfo( + name=self._get_safe_property(doc, "name", "Untitled"), + width=self._get_safe_property(doc, "width", 0), + height=self._get_safe_property(doc, "height", 0), + resolution=self._get_safe_property(doc, "resolution", 72), + mode=str(self._get_safe_property(doc, "mode", "Unknown")), + bit_depth=self._get_safe_property(doc, "bitsPerChannel", 8), + color_profile=self._get_safe_property(doc, "colorProfileName", "None"), + file_path=self._get_safe_property(doc, "fullName", None), + modified=self._get_safe_property(doc, "isDirty", False), + ) + + # Cache info + self.cache[info.name] = info + return info + + except Exception as e: + print(f"Error getting document info for {doc.name}: {e}") + return DocumentInfo( + name=self._get_safe_property(doc, "name", "Unknown"), + width=0, + height=0, + resolution=0, + mode="Unknown", + bit_depth=0, + color_profile="Unknown", + ) + + def list_documents(self, sort_by: DocumentSortBy = None, + reverse: bool = False) -> List[DocumentInfo]: + """List all open documents. + + Args: + sort_by: Sort criteria + reverse: Reverse sort order + + Returns: + List of document information + """ + try: + # Get all documents + docs = [] + for doc in self.app.documents: + info = self.get_document_info(doc) + docs.append(info) + + # Sort documents + if sort_by: + if sort_by == DocumentSortBy.NAME: + docs.sort(key=lambda x: x.name, reverse=reverse) + elif sort_by == DocumentSortBy.SIZE: + docs.sort(key=lambda x: x.size, reverse=reverse) + elif sort_by == DocumentSortBy.MODIFIED: + docs.sort(key=lambda x: x.modified, reverse=reverse) + + return docs + + except Exception as e: + print(f"Error listing documents: {e}") + return [] + + def find_documents(self, + name_pattern: str = None, + min_width: int = None, + min_height: int = None, + mode: str = None, + modified_only: bool = False) -> List[DocumentInfo]: + """Find documents matching criteria. + + Args: + name_pattern: Document name pattern + min_width: Minimum width in pixels + min_height: Minimum height in pixels + mode: Color mode + modified_only: Only modified documents + + Returns: + List of matching documents + """ + try: + docs = self.list_documents() + filtered = [] + + for doc in docs: + # Apply filters + if name_pattern and name_pattern.lower() not in doc.name.lower(): + continue + + if min_width and doc.width < min_width: + continue + + if min_height and doc.height < min_height: + continue + + if mode and doc.mode != mode: + continue + + if modified_only and not doc.modified: + continue + + filtered.append(doc) + + return filtered + + except Exception as e: + print(f"Error finding documents: {e}") + return [] + + def print_document_list(self, docs: List[DocumentInfo], + detailed: bool = False) -> None: + """Print document list. + + Args: + docs: List of documents + detailed: Show detailed information + """ + if not docs: + print("\nNo documents found") + return + + print(f"\nFound {len(docs)} document(s):") + for i, doc in enumerate(docs, 1): + if detailed: + print(f"\n{i}. {doc}") + else: + print(f"{i}. {doc.name} ({doc.width}x{doc.height})") -doc = app.documents[0] -print(doc.name) +def main() -> None: + """List documents example.""" + try: + # Initialize Photoshop + app = ps.Application() + manager = DocumentManager(app) + + # List all documents + print("\n=== All Documents ===") + docs = manager.list_documents(sort_by=DocumentSortBy.NAME) + manager.print_document_list(docs, detailed=True) + + # Find modified documents + print("\n=== Modified Documents ===") + modified = manager.find_documents(modified_only=True) + manager.print_document_list(modified) + + # Find large documents + print("\n=== Large Documents (>1000px) ===") + large = manager.find_documents(min_width=1000, min_height=1000) + manager.print_document_list(large) + + except Exception as e: + print(f"Error in main: {e}") -for doc in app.documents: - print(doc.name) +if __name__ == "__main__": + main() diff --git a/examples/list_layers.py b/examples/list_layers.py new file mode 100644 index 00000000..b6bcc5bc --- /dev/null +++ b/examples/list_layers.py @@ -0,0 +1,330 @@ +from __future__ import annotations + +from dataclasses import dataclass +from enum import Enum +from typing import Dict, List, Optional, Union + +import photoshop.api as ps +from photoshop.api._artlayer import ArtLayer +from photoshop.api._layerSet import LayerSet + +class LayerType(Enum): + """Layer types.""" + NORMAL = "ArtLayer" + GROUP = "LayerSet" + TEXT = "TextLayer" + ADJUSTMENT = "AdjustmentLayer" + SMART_OBJECT = "SmartObject" + +class LayerSortBy(Enum): + """Layer sorting options.""" + NAME = "name" + TYPE = "type" + VISIBLE = "visible" + OPACITY = "opacity" + POSITION = "position" + +@dataclass +class LayerInfo: + """Layer information.""" + name: str + type: LayerType + visible: bool + opacity: float + blend_mode: str + locked: bool + linked: bool + parent: Optional[str] = None # Parent group name + bounds: Optional[tuple] = None # Layer bounds (left, top, right, bottom) + + def __str__(self) -> str: + """Get string representation.""" + info = [ + f"Name: {self.name}", + f"Type: {self.type.name}", + f"Visible: {'Yes' if self.visible else 'No'}", + f"Opacity: {self.opacity}%", + f"Blend Mode: {self.blend_mode}", + f"Locked: {'Yes' if self.locked else 'No'}", + f"Linked: {'Yes' if self.linked else 'No'}", + ] + if self.parent: + info.append(f"Parent Group: {self.parent}") + if self.bounds: + info.append(f"Bounds: {self.bounds}") + return "\n".join(info) + +class LayerManager: + """Class for managing Photoshop layers.""" + + def __init__(self, app: ps.Application) -> None: + """Initialize layer manager. + + Args: + app: Photoshop application instance + """ + self.app = app + self.doc = None + self.cache: Dict[str, LayerInfo] = {} # Cache layer info + + def _get_safe_property(self, obj: any, prop: str, default: any = None) -> any: + """Safely get object property. + + Args: + obj: Object to get property from + prop: Property name + default: Default value if property not found + + Returns: + Property value or default + """ + try: + return getattr(obj, prop) + except Exception: + return default + + def ensure_document_exists(self) -> bool: + """Ensure a document exists. + + Returns: + bool: True if document exists + """ + try: + if len(self.app.documents) < 1: + print("Error: No document is open") + return False + self.doc = self.app.activeDocument + return True + except Exception as e: + print(f"Error checking document: {e}") + return False + + def get_layer_type(self, layer: Union[ArtLayer, LayerSet]) -> LayerType: + """Get layer type. + + Args: + layer: Layer object + + Returns: + Layer type + """ + try: + typename = self._get_safe_property(layer, "typename", "Unknown") + if typename == "ArtLayer": + # Check for specific art layer types + kind = self._get_safe_property(layer, "kind", None) + if kind == ps.LayerKind.TextLayer: + return LayerType.TEXT + if kind in [ps.LayerKind.HueSaturation, ps.LayerKind.Levels]: + return LayerType.ADJUSTMENT + if kind == ps.LayerKind.SmartObject: + return LayerType.SMART_OBJECT + return LayerType.NORMAL + if typename == "LayerSet": + return LayerType.GROUP + return LayerType.NORMAL + except Exception: + return LayerType.NORMAL + + def get_layer_info(self, layer: Union[ArtLayer, LayerSet], + parent: str = None) -> LayerInfo: + """Get layer information. + + Args: + layer: Layer object + parent: Parent group name + + Returns: + Layer information + """ + try: + # Get layer properties safely + info = LayerInfo( + name=self._get_safe_property(layer, "name", "Untitled"), + type=self.get_layer_type(layer), + visible=self._get_safe_property(layer, "visible", True), + opacity=self._get_safe_property(layer, "opacity", 100), + blend_mode=self._get_safe_property(layer, "blendMode", "normal"), + locked=self._get_safe_property(layer, "allLocked", False), + linked=self._get_safe_property(layer, "linked", False), + parent=parent, + bounds=self._get_safe_property(layer, "bounds", None), + ) + + # Cache info + self.cache[info.name] = info + return info + + except Exception as e: + print(f"Error getting layer info for {layer.name}: {e}") + return LayerInfo( + name=self._get_safe_property(layer, "name", "Unknown"), + type=LayerType.NORMAL, + visible=True, + opacity=100, + blend_mode="normal", + locked=False, + linked=False, + ) + + def list_layers(self, include_hidden: bool = True, + sort_by: LayerSortBy = None, + reverse: bool = False) -> List[LayerInfo]: + """List all layers in document. + + Args: + include_hidden: Include hidden layers + sort_by: Sort criteria + reverse: Reverse sort order + + Returns: + List of layer information + """ + try: + if not self.ensure_document_exists(): + return [] + + layers = [] + + def process_layer_set(layer_set: LayerSet, parent: str = None): + """Process layer set and its children.""" + # Process art layers in set + for layer in layer_set.artLayers: + if include_hidden or layer.visible: + info = self.get_layer_info(layer, parent) + layers.append(info) + + # Process nested layer sets + for nested_set in layer_set.layerSets: + if include_hidden or nested_set.visible: + info = self.get_layer_info(nested_set, parent) + layers.append(info) + process_layer_set(nested_set, info.name) + + # Process root level layers + for layer in self.doc.artLayers: + if include_hidden or layer.visible: + info = self.get_layer_info(layer) + layers.append(info) + + # Process root level layer sets + for layer_set in self.doc.layerSets: + if include_hidden or layer_set.visible: + info = self.get_layer_info(layer_set) + layers.append(info) + process_layer_set(layer_set, info.name) + + # Sort layers + if sort_by: + if sort_by == LayerSortBy.NAME: + layers.sort(key=lambda x: x.name, reverse=reverse) + elif sort_by == LayerSortBy.TYPE: + layers.sort(key=lambda x: x.type.name, reverse=reverse) + elif sort_by == LayerSortBy.VISIBLE: + layers.sort(key=lambda x: x.visible, reverse=reverse) + elif sort_by == LayerSortBy.OPACITY: + layers.sort(key=lambda x: x.opacity, reverse=reverse) + + return layers + + except Exception as e: + print(f"Error listing layers: {e}") + return [] + + def find_layers(self, + name_pattern: str = None, + layer_type: LayerType = None, + visible_only: bool = False, + parent_group: str = None) -> List[LayerInfo]: + """Find layers matching criteria. + + Args: + name_pattern: Layer name pattern + layer_type: Layer type + visible_only: Only visible layers + parent_group: Parent group name + + Returns: + List of matching layers + """ + try: + layers = self.list_layers(include_hidden=not visible_only) + filtered = [] + + for layer in layers: + # Apply filters + if name_pattern and name_pattern.lower() not in layer.name.lower(): + continue + + if layer_type and layer.type != layer_type: + continue + + if visible_only and not layer.visible: + continue + + if parent_group and layer.parent != parent_group: + continue + + filtered.append(layer) + + return filtered + + except Exception as e: + print(f"Error finding layers: {e}") + return [] + + def print_layer_list(self, layers: List[LayerInfo], + detailed: bool = False, + indent: bool = True) -> None: + """Print layer list. + + Args: + layers: List of layers + detailed: Show detailed information + indent: Indent nested layers + """ + if not layers: + print("\nNo layers found") + return + + print(f"\nFound {len(layers)} layer(s):") + + def get_indent(layer: LayerInfo) -> str: + """Get layer indentation.""" + if not indent or not layer.parent: + return "" + return " " * len(layer.parent.split("/")) + + for i, layer in enumerate(layers, 1): + if detailed: + print(f"\n{get_indent(layer)}{i}. {layer}") + else: + print(f"{get_indent(layer)}{i}. {layer.name} ({layer.type.name})") + +def main() -> None: + """List layers example.""" + try: + # Initialize Photoshop + app = ps.Application() + manager = LayerManager(app) + + # List all layers + print("\n=== All Layers ===") + layers = manager.list_layers(sort_by=LayerSortBy.NAME) + manager.print_layer_list(layers, detailed=True) + + # Find text layers + print("\n=== Text Layers ===") + text_layers = manager.find_layers(layer_type=LayerType.TEXT) + manager.print_layer_list(text_layers) + + # Find visible layers + print("\n=== Visible Layers ===") + visible = manager.find_layers(visible_only=True) + manager.print_layer_list(visible) + + except Exception as e: + print(f"Error in main: {e}") + +if __name__ == "__main__": + main() diff --git a/examples/load_selection.py b/examples/load_selection.py index 6f21a910..168d4260 100644 --- a/examples/load_selection.py +++ b/examples/load_selection.py @@ -1,35 +1,318 @@ -# This script will demonstrate how to load a selection from a saved alpha -# channel. +"""Selection management in Photoshop. + +This script demonstrates how to: +1. Create and manage selections +2. Save selections to alpha channels +3. Load selections from alpha channels +4. Combine selections using different modes +5. Get selection information and bounds +""" + +from __future__ import annotations + +from dataclasses import dataclass +from enum import Enum +from typing import List, Optional, Tuple, Union -# Import local modules from photoshop import Session +from photoshop.api import SelectionType + +class SelectionMode(Enum): + """Selection combination modes.""" + NEW = SelectionType.ReplaceSelection + ADD = SelectionType.ExtendSelection + SUBTRACT = SelectionType.DiminishSelection + INTERSECT = SelectionType.IntersectSelection + +@dataclass +class SelectionInfo: + """Selection information.""" + bounds: Tuple[float, float, float, float] # left, top, right, bottom + solid: bool # True if selection is solid + feather_radius: float # Feather radius in pixels + + @property + def width(self) -> float: + """Get selection width.""" + return self.bounds[2] - self.bounds[0] + + @property + def height(self) -> float: + """Get selection height.""" + return self.bounds[3] - self.bounds[1] + + @property + def area(self) -> float: + """Get selection area.""" + return self.width * self.height + + def __str__(self) -> str: + """Get string representation.""" + info = [ + f"Bounds: ({self.bounds[0]:.1f}, {self.bounds[1]:.1f}, {self.bounds[2]:.1f}, {self.bounds[3]:.1f})", + f"Width: {self.width:.1f} pixels", + f"Height: {self.height:.1f} pixels", + f"Area: {self.area:.1f} square pixels", + f"Solid: {'Yes' if self.solid else 'No'}", + f"Feather Radius: {self.feather_radius:.1f} pixels", + ] + return "\n".join(info) + +class SelectionManager: + """Class for managing Photoshop selections.""" + + def __init__(self, session: Session) -> None: + """Initialize selection manager. + + Args: + session: Photoshop session + """ + self.session = session + self.doc = None + self._ensure_pixels_units() + + def _ensure_pixels_units(self) -> None: + """Ensure ruler units are set to pixels.""" + if self.session.app.preferences.rulerUnits is not self.session.Units.Pixels: + self.session.app.preferences.rulerUnits = self.session.Units.Pixels + + def _ensure_document_exists(self) -> bool: + """Ensure a document exists. + + Returns: + bool: True if document exists + """ + try: + if len(self.session.app.documents) < 1: + print("Error: No document is open") + return False + self.doc = self.session.app.activeDocument + return True + except Exception as e: + print(f"Error checking document: {e}") + return False + + def _get_safe_property(self, obj: any, prop: str, default: any = None) -> any: + """Safely get object property. + + Args: + obj: Object to get property from + prop: Property name + default: Default value if property not found + + Returns: + Property value or default + """ + try: + value = getattr(obj, prop) + # Handle callable properties + if callable(value): + try: + value = value() + except: + value = default + return value + except Exception: + return default + + def create_selection(self, + bounds: Union[List[Tuple[float, float]], Tuple[float, float, float, float]], + mode: SelectionMode = SelectionMode.NEW, + feather: float = 0, + anti_alias: bool = True) -> bool: + """Create a selection. + + Args: + bounds: Selection bounds. Can be list of points or (left, top, right, bottom) + mode: Selection mode + feather: Feather radius in pixels + anti_alias: Use anti-aliasing + + Returns: + bool: True if successful + """ + try: + if not self._ensure_document_exists(): + return False + + # Convert bounds if needed + if isinstance(bounds, tuple) and len(bounds) == 4: + left, top, right, bottom = bounds + bounds = [ + (left, top), + (right, top), + (right, bottom), + (left, bottom), + ] + + # Create selection + self.doc.selection.select(bounds, mode.value, feather, anti_alias) + return True + + except Exception as e: + print(f"Error creating selection: {e}") + return False + + def save_to_channel(self, name: str = None) -> Optional[str]: + """Save current selection to alpha channel. + + Args: + name: Channel name (optional) + + Returns: + str: Channel name if successful, None otherwise + """ + try: + if not self._ensure_document_exists(): + return None + + # Create new channel + channel = self.doc.channels.add() + + # Store selection + self.doc.selection.store(channel) + + # Try to set name if provided + try: + if name: + channel.name = name + except: + pass # Ignore name setting errors + + return channel.name + + except Exception as e: + print(f"Error saving selection to channel: {e}") + return None + + def load_from_channel(self, + channel_name: str, + mode: SelectionMode = SelectionMode.NEW, + invert: bool = False) -> bool: + """Load selection from alpha channel. + + Args: + channel_name: Channel name + mode: Selection mode + invert: Invert selection + + Returns: + bool: True if successful + """ + try: + if not self._ensure_document_exists(): + return False + + # Find channel + channel = None + for ch in self.doc.channels: + if ch.name == channel_name: + channel = ch + break + + if not channel: + print(f"Error: Channel '{channel_name}' not found") + return False + + # Load selection + self.doc.selection.load(channel, mode.value, invert) + return True + + except Exception as e: + print(f"Error loading selection from channel: {e}") + return False + + def get_info(self) -> Optional[SelectionInfo]: + """Get selection information. + + Returns: + SelectionInfo if selection exists, None otherwise + """ + try: + if not self._ensure_document_exists(): + return None + + # Check if selection exists and get bounds + bounds = self._get_safe_property(self.doc.selection, "bounds") + if not bounds: + return None + + # Get other properties safely + solid = bool(self._get_safe_property(self.doc.selection, "solid", False)) + feather = float(self._get_safe_property(self.doc.selection, "feather", 0)) + + # Create info object + return SelectionInfo( + bounds=bounds, + solid=solid, + feather_radius=feather, + ) + + except Exception as e: + print(f"Error getting selection info: {e}") + return None + + def deselect(self) -> bool: + """Deselect current selection. + + Returns: + bool: True if successful + """ + try: + if not self._ensure_document_exists(): + return False + + self.doc.selection.deselect() + return True + + except Exception as e: + print(f"Error deselecting: {e}") + return False +def main() -> None: + """Selection management example.""" + try: + with Session() as ps: + # Create document + doc = ps.app.documents.add(320, 240) + manager = SelectionManager(ps) + + # Create first selection + print("\n=== Creating First Selection ===") + offset = 50 + bounds1 = ( + offset, offset, + doc.width - offset, doc.height - offset, + ) + manager.create_selection(bounds1, feather=2) + + # Save to channel + channel_name = manager.save_to_channel("Selection 1") + print(f"Saved to channel: {channel_name}") + + # Get selection info + info = manager.get_info() + if info: + print("\nSelection Info:") + print(info) + + # Create second selection + print("\n=== Creating Second Selection ===") + bounds2 = (0, 75, doc.width, 150) + manager.create_selection(bounds2) + + # Combine with first selection + print("\n=== Combining Selections ===") + manager.load_from_channel(channel_name, mode=SelectionMode.ADD) + + # Get final selection info + info = manager.get_info() + if info: + print("\nFinal Selection Info:") + print(info) + + except Exception as e: + print(f"Error in main: {e}") -with Session() as ps: - doc_ref = ps.app.documents.add(320, 240) - start_ruler_units = ps.app.preferences.rulerUnits - if start_ruler_units is not ps.Units.Pixels: - ps.app.Preferences.RulerUnits = ps.Units.Pixels - # Save a rectangular selection area offset by 50 pixels from the image - # border into an alpha channel. - offset = 50 - selBounds1 = ( - (offset, offset), - (doc_ref.Width - offset, offset), - (doc_ref.Width - offset, doc_ref.Height - offset), - (offset, doc_ref.Height - offset), - ) - doc_ref.selection.select(selBounds1) - selAlpha = doc_ref.channels.Add() - doc_ref.selection.store(selAlpha) - - # Now create a second wider but less tall selection. - selBounds2 = ((0, 75), (doc_ref.Width, 75), (doc_ref.Width, 150), (0, 150)) - doc_ref.selection.select(selBounds2) - - # Load the selection from the just saved alpha channel, combining it with - # the active selection. - doc_ref.selection.load(selAlpha, ps.SelectionType.ExtendSelection, False) - - # Set ruler back to where it was. - ps.app.Preferences.RulerUnits = start_ruler_units +if __name__ == "__main__": + main() diff --git a/examples/move_to_end.py b/examples/move_to_end.py index a72bc2c8..5c987366 100644 --- a/examples/move_to_end.py +++ b/examples/move_to_end.py @@ -1,19 +1,76 @@ -# Import local modules -import photoshop.api as ps +"""Layer movement management in Photoshop. + +This script demonstrates how to: +1. Move layers between groups +2. Reorder layers within groups +3. Move layers to top/bottom of stack +4. Get layer position information +""" + +from __future__ import annotations +from enum import Enum, auto + +import photoshop.api as ps -# Get photoshop instance. -app = ps.Application() +class MoveTarget(Enum): + """Layer movement targets.""" + TOP = auto() # Move to top of document/group + BOTTOM = auto() # Move to bottom of document/group + ABOVE = auto() # Move above target layer + BELOW = auto() # Move below target layer + INTO = auto() # Move into target group -# Add new document and set name to "Example for move to End." -active_document = app.documents.add(name="Example for move to End.") +def main() -> None: + """Layer movement example.""" + try: + # Initialize Photoshop + app = ps.Application() + + print("\n=== Creating Document ===") + # Create test document + doc = app.documents.add(name="Layer Movement Example") + print("Created document:", doc.name) + + print("\n=== Creating Layers ===") + # Create group + group = doc.layerSets.add() + group.name = "Test Group" + print("Created group:", group.name) + + # Create layers + layer1 = group.artLayers.add() + layer1.name = "Layer 1" + print("Created layer:", layer1.name) + + layer2 = doc.artLayers.add() + layer2.name = "Layer 2" + print("Created layer:", layer2.name) + + layer3 = doc.artLayers.add() + layer3.name = "Layer 3" + print("Created layer:", layer3.name) + + # Move layers + print("\n=== Moving Layers ===") + + # Move Layer 2 into group + print("\nMoving Layer 2 into group:") + layer2.moveToEnd(group) + print("Successfully moved Layer 2 into Test Group") + + # Move Layer 3 above Layer 1 + print("\nMoving Layer 3 above Layer 1:") + layer3.move(layer1, ps.ElementPlacement.PlaceAfter) + print("Successfully moved Layer 3 above Layer 1") + + # Move Layer 2 to bottom of group + print("\nMoving Layer 2 to bottom of group:") + layer2.moveToEnd(group) + print("Successfully moved Layer 2 to bottom") + + except Exception as e: + print(f"Error in main: {e}") -# Add a new layer set. -group_layer = active_document.layerSets.add() -# Add a layer in the group. -layer = group_layer.artLayers.add() -layer.name = "This is a child layer." -# Add a new layer in this active document top. -top_layer = active_document.artLayers.add() -top_layer.name = "This is a top layer." -top_layer.moveToEnd(group_layer) +if __name__ == "__main__": + main() diff --git a/examples/new_document.py b/examples/new_document.py index f0a00f3c..1b94090b 100644 --- a/examples/new_document.py +++ b/examples/new_document.py @@ -1,17 +1,210 @@ -# Create a new Photoshop document with diminsions 4 inches by 4 inches. -# Import local modules -import photoshop.api as ps +"""Document creation management in Photoshop. + +This script demonstrates how to: +1. Create documents with different sizes and settings +2. Use document presets +3. Handle different measurement units +4. Set document properties + +Constants Reference: +- mode: 1=Bitmap, 2=Grayscale, 3=RGB, 4=CMYK, 7=Lab, 8=Multichannel +- initial_fill: 1=White, 2=Background Color, 3=Transparent +- bits_per_channel: 1=One, 8=Eight, 16=Sixteen, 32=ThirtyTwo +""" +from __future__ import annotations + +from dataclasses import dataclass +from enum import Enum +from typing import Optional, Tuple, Union + +import photoshop.api as ps -# Start up Photoshop application -app = ps.Application() +class DocumentPreset(Enum): + """Common document presets.""" + HD_1080P = (1920, 1080, 72.0, "HD 1080p") # 1920x1080 HD + HD_720P = (1280, 720, 72.0, "HD 720p") # 1280x720 HD + SQUARE_4K = (4000, 4000, 300.0, "4K Square") # 4K Square + A4_PRINT = (2480, 3508, 300.0, "A4 Print") # A4 at 300 DPI + INSTAGRAM = (1080, 1080, 72.0, "Instagram") # Instagram Square + FACEBOOK = (1200, 630, 72.0, "Facebook") # Facebook Cover + TWITTER = (1500, 500, 72.0, "Twitter") # Twitter Header -start_ruler_units = app.preferences.rulerUnits +@dataclass +class DocumentSettings: + """Document settings.""" + width: float + height: float + resolution: float = 72.0 + name: str = "New Document" + mode: int = 3 # RGB mode + initial_fill: int = 1 # White + pixel_aspect_ratio: float = 1.0 + bits_per_channel: int = 8 # 8-bit -app.preferences.rulerUnits = ps.Units.Pixels +class DocumentCreator: + """Class for creating Photoshop documents.""" + + def __init__(self, app: ps.Application) -> None: + """Initialize document creator. + + Args: + app: Photoshop application instance + """ + self.app = app + self.original_units = None + + def _set_units(self, units: ps.Units) -> None: + """Set ruler units. + + Args: + units: Units to set + """ + self.original_units = self.app.preferences.rulerUnits + self.app.preferences.rulerUnits = units + + def _restore_units(self) -> None: + """Restore original ruler units.""" + if self.original_units is not None: + self.app.preferences.rulerUnits = self.original_units + self.original_units = None + + def _convert_to_pixels(self, value: float, from_units: ps.Units) -> float: + """Convert value to pixels. + + Args: + value: Value to convert + from_units: Units to convert from + + Returns: + float: Value in pixels + """ + if from_units == ps.Units.Pixels: + return value + + # Standard DPI for unit conversion + dpi = 72.0 + + # Convert to pixels + if from_units == ps.Units.Inches: + return value * dpi + if from_units == ps.Units.Centimeters: + return value * dpi / 2.54 + if from_units == ps.Units.Millimeters: + return value * dpi / 25.4 + if from_units == ps.Units.Points: + return value * dpi / 72.0 + if from_units == ps.Units.Picas: + return value * dpi / 6.0 + return value + + def create_from_preset(self, preset: DocumentPreset) -> Optional[ps.Document]: + """Create document from preset. + + Args: + preset: Document preset + + Returns: + Document if successful, None otherwise + """ + try: + width, height, resolution, name = preset.value + settings = DocumentSettings( + width=width, + height=height, + resolution=resolution, + name=name, + ) + return self.create_document(settings) + + except Exception as e: + print(f"Error creating document from preset: {e}") + return None + + def create_document(self, + settings: Union[DocumentSettings, Tuple[float, float]], + units: ps.Units = ps.Units.Pixels) -> Optional[ps.Document]: + """Create new document. + + Args: + settings: Document settings or (width, height) tuple + units: Units for width and height + + Returns: + Document if successful, None otherwise + """ + try: + # Convert tuple to settings + if isinstance(settings, tuple): + settings = DocumentSettings( + width=settings[0], + height=settings[1], + ) + + # Set units for creation + self._set_units(units) + + # Convert dimensions to pixels if needed + width = self._convert_to_pixels(settings.width, units) + height = self._convert_to_pixels(settings.height, units) + + # Create document + doc = self.app.documents.add( + width=width, + height=height, + resolution=settings.resolution, + name=settings.name, + mode=settings.mode, + initialFill=settings.initial_fill, + pixelAspectRatio=settings.pixel_aspect_ratio, + bitsPerChannel=settings.bits_per_channel, + ) + + return doc + + except Exception as e: + print(f"Error creating document: {e}") + return None + + finally: + # Restore original units + self._restore_units() -# Create the document -docRef = app.documents.add(1920, 1080, 72.0, "My New Document") +def main() -> None: + """Document creation example.""" + try: + # Initialize Photoshop + app = ps.Application() + creator = DocumentCreator(app) + + print("\n=== Creating Documents ===") + + # Create document from preset + print("\nCreating HD 1080p document:") + doc1 = creator.create_from_preset(DocumentPreset.HD_1080P) + if doc1: + print(f"Created document: {doc1.name} ({doc1.width}x{doc1.height})") + + # Create custom document in inches + print("\nCreating 4x4 inch document:") + settings = DocumentSettings( + width=4, + height=4, + resolution=300, + name="Print Document", + ) + doc2 = creator.create_document(settings, units=ps.Units.Inches) + if doc2: + print(f"Created document: {doc2.name} ({doc2.width}x{doc2.height})") + + # Create simple document from dimensions + print("\nCreating square document:") + doc3 = creator.create_document((1000, 1000)) + if doc3: + print(f"Created document: {doc3.name} ({doc3.width}x{doc3.height})") + + except Exception as e: + print(f"Error in main: {e}") -# Make sure to set the ruler units prior to creating the document. -app.preferences.rulerUnits = start_ruler_units +if __name__ == "__main__": + main() diff --git a/examples/open_psd.py b/examples/open_psd.py index ac03d4d7..c64cc80b 100644 --- a/examples/open_psd.py +++ b/examples/open_psd.py @@ -1,12 +1,179 @@ -# Import local modules -from photoshop import Session +"""PSD file opening and management in Photoshop. + +This script demonstrates how to: +1. Open PSD/PSB files with different methods +2. Validate files before opening +3. Handle file opening errors +4. Work with opened documents +""" + +from __future__ import annotations + +import os +from dataclasses import dataclass +from enum import Enum, auto +from pathlib import Path +from typing import Optional, Union + import photoshop.api as ps +from photoshop import Session + +class OpenMethod(Enum): + """PSD file opening methods.""" + DIRECT = auto() # Direct opening using Application + SESSION = auto() # Opening using Session context manager + +@dataclass +class OpenOptions: + """PSD file opening options.""" + path: Union[str, Path] # File path + method: OpenMethod = OpenMethod.DIRECT # Opening method + as_copy: bool = False # Open as copy + show_dialogs: bool = False # Show dialogs during open +class PSDOpener: + """Class for opening and managing PSD files.""" + + def __init__(self, app: Optional[ps.Application] = None) -> None: + """Initialize PSD opener. + + Args: + app: Optional Photoshop application instance + """ + self.app = app or ps.Application() + + def _validate_file(self, path: Union[str, Path]) -> bool: + """Validate PSD file. + + Args: + path: File path + + Returns: + bool: True if file is valid + + Raises: + FileNotFoundError: If file does not exist + ValueError: If file has invalid extension + """ + path = Path(path) + + # Check if file exists + if not path.exists(): + raise FileNotFoundError(f"File not found: {path}") + + # Check extension + if path.suffix.lower() not in [".psd", ".psb"]: + raise ValueError(f"Invalid file extension: {path.suffix}") + + return True + + def open_file(self, options: OpenOptions) -> Optional[ps.Document]: + """Open PSD file. + + Args: + options: Opening options + + Returns: + Document if successful, None otherwise + """ + try: + # Validate file + self._validate_file(options.path) + path = str(Path(options.path).absolute()) + + # Open file using selected method + if options.method == OpenMethod.DIRECT: + return self._open_direct(path, options) + return self._open_with_session(path, options) + + except Exception as e: + print(f"Error opening file: {e}") + return None + + def _open_direct(self, path: str, options: OpenOptions) -> Optional[ps.Document]: + """Open file directly using Application. + + Args: + path: Absolute file path + options: Opening options + + Returns: + Document if successful, None otherwise + """ + try: + # Set dialog preferences + original_dialogs = self.app.displayDialogs + self.app.displayDialogs = ps.DialogModes.NO if not options.show_dialogs else ps.DialogModes.ALL + + # Open document + doc = self.app.open(path, options.as_copy) + + return doc + + except Exception as e: + print(f"Error in direct open: {e}") + return None + + finally: + # Restore dialog preferences + self.app.displayDialogs = original_dialogs + + def _open_with_session(self, path: str, options: OpenOptions) -> Optional[ps.Document]: + """Open file using Session context manager. + + Args: + path: Absolute file path + options: Opening options + + Returns: + Document if successful, None otherwise + """ + try: + # Open session + with Session(path, action="open") as pss: + # Get active document + doc = pss.active_document + print(f"Opened document: {doc.name}") + return doc + + except Exception as e: + print(f"Error in session open: {e}") + return None -# style 1 -app = ps.Application() -app.load("your/psd/or/psb/file_path.psd") +def main() -> None: + """PSD file opening example.""" + try: + # Initialize opener + opener = PSDOpener() + + print("\n=== Opening PSD Files ===") + + # Example PSD file path (replace with actual path) + file_path = os.path.join(os.path.dirname(__file__), "files", "layer_comps.psd") + + # Open file directly + print("\nOpening file directly:") + options1 = OpenOptions( + path=file_path, + method=OpenMethod.DIRECT, + show_dialogs=False, + ) + doc1 = opener.open_file(options1) + if doc1: + print(f"Opened document: {doc1.name}") + + # Open file with session + print("\nOpening file with session:") + options2 = OpenOptions( + path=file_path, + method=OpenMethod.SESSION, + ) + doc2 = opener.open_file(options2) + if doc2: + print(f"Opened document: {doc2.name}") + + except Exception as e: + print(f"Error in main: {e}") -# style 2 -with Session("your/psd/or/psb/file_path.psd", action="open") as ps: - ps.echo(ps.active_document.name) +if __name__ == "__main__": + main() diff --git a/examples/operate_channels.py b/examples/operate_channels.py index 727334bd..42faef26 100644 --- a/examples/operate_channels.py +++ b/examples/operate_channels.py @@ -1,14 +1,254 @@ -"""A examples to show you how to operate active document channels.""" +"""Channel management in Photoshop. -# Import local modules +This script demonstrates how to: +1. Create and remove channels +2. Select and modify channels +3. Blend channels +4. Work with alpha channels +5. Handle channel operations +""" + +from __future__ import annotations + +from dataclasses import dataclass +from enum import Enum, auto +from typing import List, Optional + +import photoshop.api as ps from photoshop import Session +class ChannelType(Enum): + """Channel types.""" + RGB = auto() # RGB channels + ALPHA = auto() # Alpha channels + SPOT = auto() # Spot color channels + MASK = auto() # Layer mask channels + +@dataclass +class ChannelInfo: + """Channel information.""" + name: str # Channel name + type: ChannelType # Channel type + visible: bool = True # Channel visibility + color: Optional[tuple[int, int, int]] = None # Channel color (RGB) + opacity: float = 100.0 # Channel opacity (0-100) + +class ChannelManager: + """Class for managing Photoshop channels.""" + + def __init__(self, doc: ps.Document) -> None: + """Initialize channel manager. + + Args: + doc: Photoshop document + """ + self.doc = doc + + def list_channels(self) -> List[str]: + """List all channels. + + Returns: + List of channel names + """ + try: + return [str(channel.name) for channel in self.doc.channels] + + except Exception as e: + print(f"Error listing channels: {e}") + return [] + + def get_channel(self, name: str) -> Optional[ps.Channel]: + """Get channel by name. + + Args: + name: Channel name + + Returns: + Channel if found, None otherwise + """ + try: + for channel in self.doc.channels: + if str(channel.name) == name: + return channel + return None + + except Exception as e: + print(f"Error getting channel '{name}': {e}") + return None + + def create_alpha_channel(self, info: ChannelInfo) -> Optional[ps.Channel]: + """Create new alpha channel. + + Args: + info: Channel information + + Returns: + Created channel if successful, None otherwise + """ + try: + # Create channel + channel = self.doc.channels.add() + + # Set properties (some properties may not be settable) + try: + if hasattr(channel, "kind"): + channel.kind = "alpha" # Set as alpha channel + except Exception: + pass + + try: + if hasattr(channel, "visible"): + channel.visible = info.visible + except Exception: + pass + + try: + if hasattr(channel, "opacity"): + channel.opacity = info.opacity + except Exception: + pass + + return channel + + except Exception as e: + print(f"Error creating channel: {e}") + return None + + def duplicate_channel(self, + source_name: str, + target_name: str) -> Optional[ps.Channel]: + """Duplicate channel. + + Args: + source_name: Source channel name + target_name: Target channel name + + Returns: + Duplicated channel if successful, None otherwise + """ + try: + # Get source channel + source = self.get_channel(source_name) + if not source: + print(f"Source channel '{source_name}' not found") + return None + + # Create new channel + target = self.create_alpha_channel(ChannelInfo( + name=target_name, + type=ChannelType.ALPHA, + )) + + if not target: + return None + + # Copy channel data (this may require JavaScript) + # TODO: Implement channel data copying + + return target + + except Exception as e: + print(f"Error duplicating channel: {e}") + return None + + def remove_channel(self, name: str) -> bool: + """Remove channel. + + Args: + name: Channel name + + Returns: + True if successful + """ + try: + channel = self.get_channel(name) + if channel: + channel.remove() + return True + return False + + except Exception as e: + print(f"Error removing channel: {e}") + return False + + def remove_all_channels(self) -> bool: + """Remove all channels. + + Returns: + True if successful + """ + try: + self.doc.channels.removeAll() + return True + + except Exception as e: + print(f"Error removing all channels: {e}") + return False + + def select_channel(self, name: str) -> bool: + """Select channel. + + Args: + name: Channel name + + Returns: + True if successful + """ + try: + channel = self.get_channel(name) + if channel: + self.doc.activeChannels = [channel] + return True + return False + + except Exception as e: + print(f"Error selecting channel: {e}") + return False + +def main() -> None: + """Channel management example.""" + try: + # Initialize Photoshop + with Session() as pss: + doc = pss.active_document + manager = ChannelManager(doc) + + print("\n=== Channel Operations ===") + + # List channels + print("\nExisting channels:") + channels = manager.list_channels() + for channel in channels: + print(f"- {channel}") + + # Create new channel + print("\nCreating new alpha channel:") + new_channel = manager.create_alpha_channel(ChannelInfo( + name="Alpha 1", + type=ChannelType.ALPHA, + )) + if new_channel: + print("Created new alpha channel") + + # List updated channels + print("\nUpdated channels:") + channels = manager.list_channels() + for channel in channels: + print(f"- {channel}") + + # Select a channel + if channels: + print(f"\nSelecting channel '{channels[0]}':") + if manager.select_channel(channels[0]): + print(f"Selected channel: {channels[0]}") + + # Remove channel + print("\nRemoving alpha channel:") + if manager.remove_channel("Alpha 1"): + print("Removed alpha channel") + + except Exception as e: + print(f"Error in main: {e}") -with Session() as ps: - doc = ps.active_document - print(len(doc.channels)) - doc.channels.add() - doc.channels.removeAll() - channel = doc.channels.getByName("Red") - print(channel.name) - channel.remove() +if __name__ == "__main__": + main() diff --git a/examples/operate_layerSet.py b/examples/operate_layerSet.py index 6fbe4166..35f01208 100644 --- a/examples/operate_layerSet.py +++ b/examples/operate_layerSet.py @@ -1,35 +1,280 @@ -"""A examples to show you how to operate layerSet.""" +"""Layer set (group) management in Photoshop. -# Import local modules +This script demonstrates how to: +1. Create and remove layer sets +2. Manage layer set properties +3. Handle layer set hierarchy +4. Move layers between sets +5. Merge layer sets +""" + +from __future__ import annotations + +from dataclasses import dataclass +from enum import Enum +from typing import List, Optional, Union + +import photoshop.api as ps from photoshop import Session +class LayerSetBlendMode(Enum): + """Layer set blend modes.""" + NORMAL = 2 # Normal + MULTIPLY = 4 # Multiply + SCREEN = 9 # Screen + OVERLAY = 10 # Overlay + SOFT_LIGHT = 11 # Soft Light + HARD_LIGHT = 12 # Hard Light + COLOR_DODGE = 13 # Color Dodge + COLOR_BURN = 14 # Color Burn + +@dataclass +class LayerSetInfo: + """Layer set information.""" + name: str # Layer set name + opacity: float = 100.0 # Opacity (0-100) + visible: bool = True # Visibility + blend_mode: LayerSetBlendMode = LayerSetBlendMode.NORMAL # Blend mode + color: Optional[tuple[int, int, int]] = None # Color label (RGB) + +class LayerSetManager: + """Class for managing Photoshop layer sets.""" + + def __init__(self, doc: ps.Document) -> None: + """Initialize layer set manager. + + Args: + doc: Photoshop document + """ + self.doc = doc + + def list_layer_sets(self, parent: Optional[ps.LayerSet] = None) -> List[str]: + """List layer sets. + + Args: + parent: Parent layer set, None for root level + + Returns: + List of layer set names + """ + try: + sets = parent.layerSets if parent else self.doc.layerSets + return [str(layer_set.name) for layer_set in sets] + + except Exception as e: + print(f"Error listing layer sets: {e}") + return [] + + def get_layer_set(self, + name: str, + parent: Optional[ps.LayerSet] = None) -> Optional[ps.LayerSet]: + """Get layer set by name. + + Args: + name: Layer set name + parent: Parent layer set, None for root level + + Returns: + Layer set if found, None otherwise + """ + try: + sets = parent.layerSets if parent else self.doc.layerSets + for layer_set in sets: + if str(layer_set.name) == name: + return layer_set + return None + + except Exception as e: + print(f"Error getting layer set '{name}': {e}") + return None + + def create_layer_set(self, + info: LayerSetInfo, + parent: Optional[ps.LayerSet] = None) -> Optional[ps.LayerSet]: + """Create new layer set. + + Args: + info: Layer set information + parent: Parent layer set, None for root level + + Returns: + Created layer set if successful, None otherwise + """ + try: + # Create layer set + sets = parent.layerSets if parent else self.doc.layerSets + layer_set = sets.add() + + # Set properties + try: + layer_set.name = info.name + except Exception: + pass + + try: + layer_set.opacity = info.opacity + except Exception: + pass + + try: + layer_set.visible = info.visible + except Exception: + pass + + try: + layer_set.blendMode = info.blend_mode.value + except Exception: + pass + + return layer_set + + except Exception as e: + print(f"Error creating layer set: {e}") + return None + + def duplicate_layer_set(self, + source: Union[str, ps.LayerSet], + new_name: str) -> Optional[ps.LayerSet]: + """Duplicate layer set. + + Args: + source: Source layer set or name + new_name: New layer set name + + Returns: + Duplicated layer set if successful, None otherwise + """ + try: + # Get source layer set + if isinstance(source, str): + source = self.get_layer_set(source) + + if not source: + return None + + # Duplicate layer set + duplicate = source.duplicate() + + # Set name + try: + duplicate.name = new_name + except Exception: + pass + + return duplicate + + except Exception as e: + print(f"Error duplicating layer set: {e}") + return None + + def merge_layer_set(self, + target: Union[str, ps.LayerSet]) -> Optional[ps.ArtLayer]: + """Merge layer set. + + Args: + target: Target layer set or name + + Returns: + Merged layer if successful, None otherwise + """ + try: + # Get target layer set + if isinstance(target, str): + target = self.get_layer_set(target) + + if not target: + return None + + # Merge layer set + return target.merge() + + except Exception as e: + print(f"Error merging layer set: {e}") + return None + + def move_to_layer_set(self, + layer: Union[ps.ArtLayer, ps.LayerSet], + target: Union[str, ps.LayerSet]) -> bool: + """Move layer to layer set. + + Args: + layer: Layer to move + target: Target layer set or name + + Returns: + True if successful + """ + try: + # Get target layer set + if isinstance(target, str): + target = self.get_layer_set(target) + + if not target: + return False + + # Move layer + layer.move(target, ps.ElementPlacement.PlaceInside) + return True + + except Exception as e: + print(f"Error moving layer: {e}") + return False + +def main() -> None: + """Layer set management example.""" + try: + # Initialize Photoshop + with Session(action="new_document") as pss: + doc = pss.active_document + manager = LayerSetManager(doc) + + print("\n=== Layer Set Operations ===") + + # Create layer set + print("\nCreating layer set:") + layer_set1 = manager.create_layer_set(LayerSetInfo( + name="Group 1", + opacity=80, + blend_mode=LayerSetBlendMode.OVERLAY, + )) + if layer_set1: + print(f"Created layer set: {layer_set1.name}") + + # Create nested layer set + print("\nCreating nested layer set:") + layer_set2 = manager.create_layer_set( + LayerSetInfo(name="Nested Group"), + parent=layer_set1, + ) + if layer_set2: + print(f"Created nested layer set: {layer_set2.name}") + + # List layer sets + print("\nLayer sets at root level:") + sets = manager.list_layer_sets() + for set_name in sets: + print(f"- {set_name}") + + # Create art layer + print("\nCreating and moving art layer:") + layer = doc.artLayers.add() + if manager.move_to_layer_set(layer, layer_set2): + print("Moved layer to nested group") + + # Duplicate layer set + print("\nDuplicating layer set:") + dup_set = manager.duplicate_layer_set(layer_set1, "Group 1 Copy") + if dup_set: + print(f"Duplicated layer set: {dup_set.name}") + + # Merge layer set + print("\nMerging layer set:") + merged = manager.merge_layer_set(dup_set) + if merged: + print(f"Merged layer set into: {merged.name}") + + except Exception as e: + print(f"Error in main: {e}") -with Session(action="new_document") as ps: - docRef = ps.active_document - # Add a new layerSet. - new_layer_set = docRef.layerSets.add() - # Print the layerSet count. - ps.echo(docRef.layerSets.length) - ps.echo(len(docRef.layerSets)) - # Rename the layerSet. - docRef.layerSets[0].name = "New Name" - ps.echo(new_layer_set.name) - - # Change the layerSet opacity - new_layer_set.opacity = 90 - ps.echo(new_layer_set.opacity) - - # Duplicate the layerSet. - duplicate_layer_set = new_layer_set.duplicate() - # Add a new artLayer in current active document. - layer = docRef.artLayers.add() - # Move the artLayer under the duplicate layerSet. - layer.move(duplicate_layer_set, ps.ElementPlacement.PlaceInside) - # Merge the layerSet. - merged_layer = duplicate_layer_set.merge() - ps.echo(merged_layer.name) - - # Set visible. - new_layer_set.visible = False - - merged_layer.remove() +if __name__ == "__main__": + main() diff --git a/examples/photoshop_session.py b/examples/photoshop_session.py index f7672c95..e7515edf 100644 --- a/examples/photoshop_session.py +++ b/examples/photoshop_session.py @@ -1,30 +1,288 @@ -"""Add slate information dynamically.""" +"""Photoshop session management and template processing. -# Import built-in modules -from datetime import datetime +This script demonstrates how to: +1. Manage Photoshop sessions +2. Open and process template files +3. Replace text content dynamically +4. Export files in different formats +5. Handle session errors +""" + +from __future__ import annotations + +import logging import os +from dataclasses import dataclass +from datetime import datetime +from enum import Enum, auto +from pathlib import Path from tempfile import mkdtemp +from typing import Any, Dict, Optional, Union -# Import third-party modules -import examples._psd_files as psd # Import from examples. - -# Import local modules +import examples._psd_files as psd # Import from examples +import photoshop.api as ps from photoshop import Session +# Configure logging +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", +) +logger = logging.getLogger(__name__) + +class ExportFormat(Enum): + """Export file formats.""" + JPEG = auto() # JPEG format + PNG = auto() # PNG format + PSD = auto() # PSD format + TIFF = auto() # TIFF format + PDF = auto() # PDF format + +@dataclass +class SaveOptions: + """Save options for different formats.""" + format: ExportFormat # Export format + quality: int = 100 # Quality (1-100, for JPEG) + compression: bool = True # Use compression + layers: bool = True # Preserve layers + icc_profile: bool = True # Include ICC profile + resolution: float = 72.0 # Resolution in DPI -PSD_FILE = psd.get_psd_files() -file_path = PSD_FILE["slate_template.psd"] +class SessionManager: + """Class for managing Photoshop sessions and templates.""" + + def __init__(self, template_path: Optional[str] = None) -> None: + """Initialize session manager. + + Args: + template_path: Path to template file + """ + self.template_path = template_path + self.session: Optional[Session] = None + self.doc: Optional[ps.Document] = None + + def __enter__(self) -> SessionManager: + """Enter context manager.""" + self.start_session() + return self + + def __exit__(self, exc_type, exc_val, exc_tb) -> None: + """Exit context manager.""" + self.close_session() + + def start_session(self) -> None: + """Start Photoshop session.""" + try: + if self.template_path: + self.session = Session( + self.template_path, + action="open", + auto_close=True, + ) + else: + self.session = Session(action="new_document") + + self.session.__enter__() + self.doc = self.session.active_document + logger.info("Started Photoshop session") + + except Exception as e: + logger.error(f"Error starting session: {e}") + raise + + def close_session(self) -> None: + """Close Photoshop session.""" + try: + if self.session: + self.session.__exit__(None, None, None) + logger.info("Closed Photoshop session") + + except Exception as e: + logger.error(f"Error closing session: {e}") + raise + + def get_layer_set(self, name: str) -> Optional[ps.LayerSet]: + """Get layer set by name. + + Args: + name: Layer set name + + Returns: + Layer set if found, None otherwise + """ + try: + if not self.doc: + return None + + return self.doc.layerSets.getByName(name) + + except Exception as e: + logger.error(f"Error getting layer set '{name}': {e}") + return None + + def replace_text_content(self, + layer_set_name: str, + data: Dict[str, Any]) -> bool: + """Replace text content in template. + + Args: + layer_set_name: Layer set name + data: Replacement data + + Returns: + True if successful + """ + try: + layer_set = self.get_layer_set(layer_set_name) + if not layer_set: + logger.error(f"Layer set '{layer_set_name}' not found") + return False + + for layer in layer_set.layers: + if layer.kind == self.session.LayerKind.TextLayer: + key = layer.textItem.contents.strip() + if key in data: + layer.textItem.contents = str(data[key]) + + logger.info("Replaced text content successfully") + return True + + except Exception as e: + logger.error(f"Error replacing text content: {e}") + return False + + def save_as_jpeg(self, file_path: Union[str, Path], quality: int = 90) -> bool: + """Save document as JPEG. + + Args: + file_path: Output file path + quality: JPEG quality (1-100) + + Returns: + True if successful + """ + try: + if not self.doc or not self.session: + logger.error("No active document or session") + return False + + # Ensure output directory exists + file_path = Path(file_path) + file_path.parent.mkdir(parents=True, exist_ok=True) + + # Convert path to Photoshop format (forward slashes) + ps_path = str(file_path).replace("\\", "/") + + # Create JavaScript code for export + js_code = """ + try { + var doc = app.activeDocument; + var file = new File("PATH"); + var opts = new ExportOptionsSaveForWeb(); + opts.format = SaveDocumentType.JPEG; + opts.includeProfile = true; + opts.quality = QUALITY; + opts.optimized = true; + doc.exportDocument(file, ExportType.SAVEFORWEB, opts); + $.writeln("JPEG exported successfully"); + } catch(e) { + $.writeln("Error: " + e); + } + """ + + # Replace placeholders + js_code = js_code.replace("PATH", ps_path) + js_code = js_code.replace("QUALITY", str(quality)) + + # Execute JavaScript code + logger.info(f"Saving JPEG with quality {quality} to: {file_path}") + result = self.session.app.doJavaScript(js_code) + logger.info(f"JavaScript result: {result}") + + if os.path.exists(file_path): + logger.info("Save successful") + return True + logger.error("Save failed: File not created") + return False + + except Exception as e: + logger.error(f"Error saving JPEG: {e}") + return False + + def save_as_psd(self, file_path: Union[str, Path]) -> bool: + """Save document as PSD. + + Args: + file_path: Output file path + + Returns: + True if successful + """ + try: + if not self.doc or not self.session: + logger.error("No active document or session") + return False + + # Ensure output directory exists + file_path = Path(file_path) + file_path.parent.mkdir(parents=True, exist_ok=True) + + # Create save options + save_options = self.session.PhotoshopSaveOptions() + save_options.alphaChannels = True + save_options.layers = True + save_options.spotColors = True + + # Save file + logger.info(f"Saving PSD to: {file_path}") + self.doc.saveAs(str(file_path), save_options) + logger.info("Save successful") + return True + + except Exception as e: + logger.error(f"Error saving PSD: {e}") + return False -with Session(file_path, action="open", auto_close=True) as ps: - layer_set = ps.active_document.layerSets.getByName("template") - data = { - "project name": "test_project", - "datetime": datetime.today().strftime("%Y-%m-%d"), - } - for layer in layer_set.layers: - if layer.kind == ps.LayerKind.TextLayer: - layer.textItem.contents = data[layer.textItem.contents.strip()] +def main() -> None: + """Template processing example.""" + try: + # Get template file + template_path = psd.get_psd_files()["slate_template.psd"] + + # Create output directory + output_dir = Path(mkdtemp("photoshop-python-api")) + logger.info(f"Created output directory: {output_dir}") + + # Prepare replacement data + data = { + "project name": "Test Project", + "datetime": datetime.today().strftime("%Y-%m-%d"), + } + + # Process template + with SessionManager(template_path) as manager: + print("\n=== Template Processing ===") + + # Replace text content + print("\nReplacing text content:") + if manager.replace_text_content("template", data): + print("Text content replaced successfully") + + # Save as JPEG + print("\nSaving as JPEG:") + jpg_path = output_dir / "slate.jpg" + if manager.save_as_jpeg(jpg_path, quality=90): + print(f"Saved JPEG to: {jpg_path}") + os.startfile(str(jpg_path)) + + # Save as PSD + print("\nSaving as PSD:") + psd_path = output_dir / "slate.psd" + if manager.save_as_psd(psd_path): + print(f"Saved PSD to: {psd_path}") + + except Exception as e: + logger.error(f"Error in main: {e}") - jpg_file = os.path.join(mkdtemp("photoshop-python-api"), "slate.jpg") - ps.active_document.saveAs(jpg_file, ps.JPEGSaveOptions()) - os.startfile(jpg_file) +if __name__ == "__main__": + main() diff --git a/examples/replace_images.py b/examples/replace_images.py index 24642cdb..be020670 100644 --- a/examples/replace_images.py +++ b/examples/replace_images.py @@ -1,34 +1,189 @@ -"""Replace the image of the current active layer with a new image.""" +"""Replace images in Photoshop layers. -# Import third-party modules -import examples._psd_files as psd # Import from examples. +This script demonstrates how to: +1. Replace image contents in a placed layer +2. Resize replaced image to match original dimensions +3. Handle layer bounds and dimensions +4. Manage image replacement errors +""" -# Import local modules -from photoshop import Session +from __future__ import annotations + +import logging +from dataclasses import dataclass +from pathlib import Path +from typing import Optional, Tuple, Union +import examples._psd_files as psd # Import from examples +import photoshop.api as ps +from photoshop import Session -PSD_FILE = psd.get_psd_files() +# Configure logging +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", +) +logger = logging.getLogger(__name__) +@dataclass +class ImageBounds: + """Image bounds information.""" + left: float + top: float + right: float + bottom: float + + @property + def width(self) -> float: + """Get image width.""" + return self.right - self.left + + @property + def height(self) -> float: + """Get image height.""" + return self.bottom - self.top + + @classmethod + def from_bounds(cls, bounds: Tuple[float, float, float, float]) -> ImageBounds: + """Create ImageBounds from bounds tuple. + + Args: + bounds: Bounds tuple (left, top, right, bottom) + + Returns: + ImageBounds object + """ + return cls(bounds[0], bounds[1], bounds[2], bounds[3]) -with Session(PSD_FILE["replace_images.psd"], action="open") as ps: - active_layer = ps.active_document.activeLayer - bounds = active_layer.bounds - print(f"current layer {active_layer.name}: {bounds}") - input_file = PSD_FILE["red_100x200.png"] - replace_contents = ps.app.stringIDToTypeID("placedLayerReplaceContents") - desc = ps.ActionDescriptor - idnull = ps.app.charIDToTypeID("null") - desc.putPath(idnull, input_file) - ps.app.executeAction(replace_contents, desc) +class ImageReplacer: + """Class for replacing images in Photoshop layers.""" + + def __init__(self, session: Session) -> None: + """Initialize image replacer. + + Args: + session: Photoshop session + """ + self.session = session + self.doc = session.active_document + self.app = session.app + + def get_active_layer(self) -> Optional[ps.ArtLayer]: + """Get active layer. + + Returns: + Active layer if exists, None otherwise + """ + try: + return self.doc.activeLayer + + except Exception as e: + logger.error(f"Error getting active layer: {e}") + return None + + def get_layer_bounds(self, layer: ps.ArtLayer) -> Optional[ImageBounds]: + """Get layer bounds. + + Args: + layer: Layer to get bounds for + + Returns: + Layer bounds if successful, None otherwise + """ + try: + bounds = layer.bounds + return ImageBounds.from_bounds(bounds) + + except Exception as e: + logger.error(f"Error getting layer bounds: {e}") + return None + + def replace_image(self, image_path: Union[str, Path]) -> bool: + """Replace image in active layer. + + Args: + image_path: Path to replacement image + + Returns: + True if successful + """ + try: + # Get active layer + layer = self.get_active_layer() + if not layer: + logger.error("No active layer") + return False + + # Get original bounds + original_bounds = self.get_layer_bounds(layer) + if not original_bounds: + logger.error("Could not get layer bounds") + return False + + logger.info(f"Original layer '{layer.name}' bounds: {original_bounds}") + + # Replace image contents + replace_contents = self.app.stringIDToTypeID("placedLayerReplaceContents") + desc = self.session.ActionDescriptor + idnull = self.app.charIDToTypeID("null") + desc.putPath(idnull, str(image_path)) + + try: + self.app.executeAction(replace_contents, desc) + logger.info("Image contents replaced successfully") + except Exception as e: + logger.error(f"Error replacing image contents: {e}") + return False + + # Get new bounds + layer = self.get_active_layer() + if not layer: + logger.error("Could not get active layer after replacement") + return False + + current_bounds = self.get_layer_bounds(layer) + if not current_bounds: + logger.error("Could not get layer bounds after replacement") + return False + + logger.info(f"New layer bounds: {current_bounds}") + + # Calculate and apply resize + try: + new_size = original_bounds.width / current_bounds.width * 100 + layer.resize(new_size, new_size, self.session.AnchorPosition.MiddleCenter) + logger.info(f"Resized layer to {new_size}%") + return True + + except Exception as e: + logger.error(f"Error resizing layer: {e}") + return False + + except Exception as e: + logger.error(f"Error replacing image: {e}") + return False - # replaced image. - active_layer = ps.active_document.activeLayer - current_bounds = active_layer.bounds - width = bounds[2] - bounds[0] - height = bounds[3] - bounds[1] +def main() -> None: + """Image replacement example.""" + try: + # Get PSD files + psd_files = psd.get_psd_files() + template_path = psd_files["replace_images.psd"] + replacement_path = psd_files["red_100x200.png"] + + # Process template + with Session(template_path, action="open") as pss: + print("\n=== Image Replacement ===") + + # Replace image + replacer = ImageReplacer(pss) + if replacer.replace_image(replacement_path): + print("Image replaced successfully") + else: + print("Failed to replace image") + + except Exception as e: + logger.error(f"Error in main: {e}") - current_width = current_bounds[2] - current_bounds[0] - current_height = current_bounds[3] - current_bounds[1] - new_size = width / current_width * 100 - active_layer.resize(new_size, new_size, ps.AnchorPosition.MiddleCenter) - print(f"current layer {active_layer.name}: {current_bounds}") +if __name__ == "__main__": + main() diff --git a/examples/revert_changes.py b/examples/revert_changes.py index d5463a58..724e42d4 100644 --- a/examples/revert_changes.py +++ b/examples/revert_changes.py @@ -1,15 +1,161 @@ -"""This example demonstrates how to roll back history.""" +"""Photoshop history state management. -# Import local modules +This script demonstrates how to: +1. Manage document history states +2. Save and restore document states +3. Track history state changes +4. Handle history state errors +""" + +from __future__ import annotations + +import logging +from dataclasses import dataclass +from typing import List, Optional + +import photoshop.api as ps from photoshop import Session +# Configure logging +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", +) +logger = logging.getLogger(__name__) + +@dataclass +class HistoryState: + """History state information.""" + name: str + state: ps.HistoryState + + @classmethod + def from_state(cls, state: ps.HistoryState) -> HistoryState: + """Create HistoryState from Photoshop history state. + + Args: + state: Photoshop history state + + Returns: + HistoryState object + """ + return cls(state.name, state) + +class HistoryManager: + """Class for managing Photoshop document history.""" + + def __init__(self, session: Session) -> None: + """Initialize history manager. + + Args: + session: Photoshop session + """ + self.session = session + self.doc = session.active_document + + def get_current_state(self) -> Optional[HistoryState]: + """Get current history state. + + Returns: + Current history state if exists, None otherwise + """ + try: + state = self.doc.activeHistoryState + return HistoryState.from_state(state) + + except Exception as e: + logger.error(f"Error getting current state: {e}") + return None + + def get_all_states(self) -> List[HistoryState]: + """Get all history states. + + Returns: + List of history states + """ + try: + states = [] + for state in self.doc.historyStates: + states.append(HistoryState.from_state(state)) + return states + + except Exception as e: + logger.error(f"Error getting history states: {e}") + return [] + + def restore_state(self, state: HistoryState) -> bool: + """Restore to specified history state. + + Args: + state: History state to restore to + + Returns: + True if successful + """ + try: + self.doc.activeHistoryState = state.state + logger.info(f"Restored to state: {state.name}") + return True + + except Exception as e: + logger.error(f"Error restoring state: {e}") + return False + + def add_layer(self) -> bool: + """Add new layer and return success. + + Returns: + True if successful + """ + try: + self.doc.artLayers.add() + logger.info("Added new layer") + return True + + except Exception as e: + logger.error(f"Error adding layer: {e}") + return False + +def main() -> None: + """History management example.""" + try: + # Create new document + with Session(action="new_document") as pss: + print("\n=== History Management ===") + + # Create history manager + manager = HistoryManager(pss) + + # Get initial state + initial_state = manager.get_current_state() + if initial_state: + print(f"\nInitial state: {initial_state.name}") + + # Add new layer + if manager.add_layer(): + print("\nAdded new layer") + + # Get current state + current_state = manager.get_current_state() + if current_state: + print(f"Current state: {current_state.name}") + + # List all states + print("\nAll history states:") + for state in manager.get_all_states(): + print(f"- {state.name}") + + # Restore to initial state + if initial_state and manager.restore_state(initial_state): + print(f"\nRestored to initial state: {initial_state.name}") + + # Verify current state + final_state = manager.get_current_state() + if final_state: + print(f"Final state: {final_state.name}") + + except Exception as e: + logger.error(f"Error in main: {e}") -with Session() as ps: - doc = ps.active_document - old_state = doc.activeHistoryState - print(old_state.name) - doc.artLayers.add() - last_state = doc.activeHistoryState - print(last_state.name) - doc.activeHistoryState = old_state - print(doc.activeHistoryState.name) +if __name__ == "__main__": + main() diff --git a/examples/rotate_layer.py b/examples/rotate_layer.py index 942244bb..0bd58316 100644 --- a/examples/rotate_layer.py +++ b/examples/rotate_layer.py @@ -1,23 +1,294 @@ -"""This scripts demonstrates how to rotate a layer 45 degrees clockwise. +"""Photoshop layer rotation management. + +This script demonstrates how to: +1. Rotate layers in Photoshop documents +2. Handle layer selection and validation +3. Manage layer transformations +4. Handle rotation errors References: https://github.com/lohriialo/photoshop-scripting-python/blob/master/RotateLayer.py - """ -# Import local modules +from __future__ import annotations + +import logging +from dataclasses import dataclass +from enum import Enum, auto +from typing import Optional + import photoshop.api as ps +from photoshop import Session + +# Configure logging +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", +) +logger = logging.getLogger(__name__) + +class RotationDirection(Enum): + """Rotation direction options.""" + CLOCKWISE = auto() + COUNTERCLOCKWISE = auto() + +@dataclass +class LayerInfo: + """Layer information.""" + name: str + type_name: str + is_background: bool + is_visible: bool + + @classmethod + def from_layer(cls, layer: ps.ArtLayer) -> LayerInfo: + """Create LayerInfo from Photoshop layer. + + Args: + layer: Photoshop layer + + Returns: + LayerInfo object + """ + return cls( + layer.name, + layer.typename, + layer.isBackgroundLayer, + layer.visible, + ) +class LayerManager: + """Class for managing Photoshop layer operations.""" + + def __init__(self, session: Session) -> None: + """Initialize layer manager. + + Args: + session: Photoshop session + """ + self.session = session + self.doc = session.active_document + self.app = session.app + + def get_active_layer(self) -> Optional[ps.ArtLayer]: + """Get active layer. + + Returns: + Active layer if exists, None otherwise + """ + try: + return self.doc.activeLayer + + except Exception as e: + logger.error(f"Error getting active layer: {e}") + return None + + def get_layer_info(self, layer: ps.ArtLayer) -> Optional[LayerInfo]: + """Get layer information. + + Args: + layer: Layer to get information for + + Returns: + Layer information if successful, None otherwise + """ + try: + return LayerInfo.from_layer(layer) + + except Exception as e: + logger.error(f"Error getting layer info: {e}") + return None + + def rotate_layer(self, + angle: float, + direction: RotationDirection = RotationDirection.CLOCKWISE, + layer: Optional[ps.ArtLayer] = None) -> bool: + """Rotate layer by specified angle. + + Args: + angle: Rotation angle in degrees + direction: Rotation direction + layer: Layer to rotate, uses active layer if None + + Returns: + True if successful + """ + try: + # Get target layer + target_layer = layer or self.get_active_layer() + if not target_layer: + logger.error("No target layer") + return False + + # Get layer info + layer_info = self.get_layer_info(target_layer) + if not layer_info: + logger.error("Could not get layer info") + return False + + # Check if background layer + if layer_info.is_background: + logger.error("Cannot rotate background layer") + return False + + # Calculate rotation angle + if direction == RotationDirection.COUNTERCLOCKWISE: + angle = -angle + + # Create JavaScript code for rotation + js_code = """ + try { + var doc = app.activeDocument; + var layer = doc.activeLayer; + + // Select layer + doc.activeLayer = layer; + + // Start transform + var idTrnf = charIDToTypeID('Trnf'); + var desc = new ActionDescriptor(); + var ref = new ActionReference(); + ref.putEnumerated(charIDToTypeID('Lyr '), charIDToTypeID('Ordn'), charIDToTypeID('Trgt')); + desc.putReference(charIDToTypeID('null'), ref); + executeAction(idTrnf, desc, DialogModes.NO); + + // Rotate layer + var idRtte = charIDToTypeID('Rtte'); + var desc2 = new ActionDescriptor(); + desc2.putUnitDouble(charIDToTypeID('Angl'), charIDToTypeID('#Ang'), ANGLE); + executeAction(idRtte, desc2, DialogModes.NO); + + // Commit transform + var idTrnf = charIDToTypeID('Trnf'); + var desc3 = new ActionDescriptor(); + executeAction(idTrnf, desc3, DialogModes.NO); + + $.writeln("Rotation successful"); + } catch(e) { + // Cancel transform + try { + var idRset = charIDToTypeID('Rset'); + var desc4 = new ActionDescriptor(); + executeAction(idRset, desc4, DialogModes.NO); + } catch(e2) {} + $.writeln("Error: " + e); + } + """ + + # Replace placeholders + js_code = js_code.replace("ANGLE", str(angle)) + + # Execute JavaScript code + logger.info(f"Rotating layer '{layer_info.name}' by {angle} degrees") + result = self.app.doJavaScript(js_code) + logger.info(f"JavaScript result: {result}") + + if "successful" in str(result): + return True + logger.error(f"Rotation failed: {result}") + return False + + except Exception as e: + logger.error(f"Error in rotate_layer: {e}") + return False -app = ps.Application() +def main() -> None: + """Layer rotation example.""" + try: + # Create new document + with Session(action="new_document") as pss: + print("\n=== Layer Rotation ===") + + # Create layer manager + manager = LayerManager(pss) + + # Add new layer + pss.active_document.artLayers.add() + + # Draw something on the layer + js_code = """ + try { + var doc = app.activeDocument; + var layer = doc.activeLayer; + + // Create rectangle selection + var idsetd = charIDToTypeID("setd"); + var desc = new ActionDescriptor(); + var idnull = charIDToTypeID("null"); + var ref = new ActionReference(); + var idChnl = charIDToTypeID("Chnl"); + var idfsel = charIDToTypeID("fsel"); + ref.putProperty(idChnl, idfsel); + desc.putReference(idnull, ref); + var idT = charIDToTypeID("T "); + var desc2 = new ActionDescriptor(); + var idTop = charIDToTypeID("Top "); + desc2.putDouble(idTop, 100); + var idLeft = charIDToTypeID("Left"); + desc2.putDouble(idLeft, 100); + var idBtom = charIDToTypeID("Btom"); + desc2.putDouble(idBtom, 200); + var idRght = charIDToTypeID("Rght"); + desc2.putDouble(idRght, 200); + var idRctn = charIDToTypeID("Rctn"); + desc.putObject(idT, idRctn, desc2); + executeAction(idsetd, desc, DialogModes.NO); + + // Fill selection + var idFl = charIDToTypeID("Fl "); + var desc = new ActionDescriptor(); + var idUsng = charIDToTypeID("Usng"); + var idFlCn = charIDToTypeID("FlCn"); + var idFrgC = charIDToTypeID("FrgC"); + desc.putEnumerated(idUsng, idFlCn, idFrgC); + executeAction(idFl, desc, DialogModes.NO); + + // Deselect + var idsetd = charIDToTypeID("setd"); + var desc = new ActionDescriptor(); + var idnull = charIDToTypeID("null"); + var ref = new ActionReference(); + var idChnl = charIDToTypeID("Chnl"); + var idfsel = charIDToTypeID("fsel"); + ref.putProperty(idChnl, idfsel); + desc.putReference(idnull, ref); + var idT = charIDToTypeID("T "); + var idOrdn = charIDToTypeID("Ordn"); + var idNone = charIDToTypeID("None"); + desc.putEnumerated(idT, idOrdn, idNone); + executeAction(idsetd, desc, DialogModes.NO); + + $.writeln("Drawing successful"); + } catch(e) { + $.writeln("Error: " + e); + } + """ + + # Execute JavaScript code + logger.info("Drawing rectangle on layer") + pss.app.doJavaScript(js_code) + + # Get active layer + layer = manager.get_active_layer() + if layer: + # Get layer info + layer_info = manager.get_layer_info(layer) + if layer_info: + print(f"\nActive layer: {layer_info.name}") + print(f"Type: {layer_info.type_name}") + print(f"Is background: {layer_info.is_background}") + print(f"Is visible: {layer_info.is_visible}") + + # Rotate layer + if manager.rotate_layer(45.0): + print("\nRotated layer 45 degrees clockwise") + + # Rotate layer counterclockwise + if manager.rotate_layer(30.0, RotationDirection.COUNTERCLOCKWISE): + print("Rotated layer 30 degrees counterclockwise") + + except Exception as e: + logger.error(f"Error in main: {e}") -if len(app.documents) > 0: - print(app.activeDocument.activeLayer.typename) - if not app.activeDocument.activeLayer.isBackgroundLayer: - docRef = app.activeDocument - layerRef = docRef.layers[0] - layerRef.rotate(45.0) - else: - print("Operation cannot be performed on background layer") -else: - print("You must have at least one open document to run this script!") +if __name__ == "__main__": + main() diff --git a/examples/run_batch.py b/examples/run_batch.py index 5e791397..c2e1094f 100644 --- a/examples/run_batch.py +++ b/examples/run_batch.py @@ -1,16 +1,17 @@ -# Import built-in modules -import os - -# Import local modules -from photoshop import Session - - -root = "your/images/root" -files = [] -for name in os.listdir(root): - files.append(os.path.join(root, name)) -with Session() as api: - options = api.BatchOptions() - options.destination = 3 - options.destinationFolder = "c:\\test" - api.app.batch(files=files, actionName="Quadrant Colors", actionSet="Default Actions", options=options) +# Import built-in modules +from __future__ import annotations + +import os + +# Import local modules +from photoshop import Session + +root = "your/images/root" +files = [] +for name in os.listdir(root): + files.append(os.path.join(root, name)) +with Session() as api: + options = api.BatchOptions() + options.destination = 3 + options.destinationFolder = "c:\\test" + api.app.batch(files=files, actionName="Quadrant Colors", actionSet="Default Actions", options=options) diff --git a/examples/save_as_pdf.py b/examples/save_as_pdf.py index 03096bc9..f4f2a646 100644 --- a/examples/save_as_pdf.py +++ b/examples/save_as_pdf.py @@ -1,21 +1,116 @@ -"""Save current active document as a PDF file.""" +"""Save current active document as a PDF file. + +This script demonstrates different ways to save a Photoshop document as PDF, +including various PDF options and settings. +""" + # Import built-in modules +from __future__ import annotations + +import logging import os from tempfile import mkdtemp # Import local modules from photoshop import Session +from photoshop.api.enumerations import LayerKind + +# Configure logging +logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s") +logger = logging.getLogger(__name__) + +def save_as_pdf_simple(): + """Save document as PDF with basic settings.""" + try: + with Session() as ps: + # Create a simple document for testing + doc = ps.app.documents.add( + width=500, + height=500, + resolution=72, + name="PDFTest", + ) + + # Add some content + text_layer = doc.artLayers.add() + text_layer.kind = LayerKind.TextLayer + text_layer.textItem.contents = "Hello PDF!" + text_layer.textItem.size = 72 + + # Basic PDF options + option = ps.PDFSaveOptions( + jpegQuality=12, # Highest quality + layers=True, # Preserve layers + view=True, # Open in PDF viewer after saving + ) + + # Save the file + pdf_path = os.path.join(mkdtemp(), "test_simple.pdf") + doc.saveAs(pdf_path, option) + logger.info(f"PDF saved successfully: {pdf_path}") + + # Close the document + doc.close() + + except Exception as e: + logger.error(f"Failed to save PDF: {e!s}") + raise + +def save_as_pdf_advanced(): + """Save document as PDF with advanced settings.""" + try: + with Session() as ps: + # Create a test document + doc = ps.app.documents.add( + width=1000, + height=1000, + resolution=300, + name="PDFTestAdvanced", + ) + + # Add some content + text_layer = doc.artLayers.add() + text_layer.kind = LayerKind.TextLayer + text_layer.textItem.contents = "Advanced PDF Settings" + text_layer.textItem.size = 72 + + # Advanced PDF options + option = ps.PDFSaveOptions() + + # Image quality settings + option.jpegQuality = 12 # Highest quality + + # PDF structure settings + option.layers = True # Preserve layers + option.preserveEditing = True # Keep editing capabilities + option.embedThumbnail = True # Include thumbnail + option.optimizeForWeb = True # Optimize for web viewing + option.view = True # Open in PDF viewer after saving + + # Save the file + pdf_path = os.path.join(mkdtemp(), "test_advanced.pdf") + doc.saveAs(pdf_path, option) + logger.info(f"PDF saved successfully with advanced options: {pdf_path}") + # Close the document + doc.close() -with Session() as ps: - option = ps.PDFSaveOptions(jpegQuality=12, layers=True, view=True) - pdf = os.path.join(mkdtemp(), "test.pdf") - ps.active_document.saveAs(pdf, option) + except Exception as e: + logger.error(f"Failed to save PDF with advanced options: {e!s}") + raise -with Session() as ps: - option = ps.PDFSaveOptions() - option.jpegQuality = 12 - option.layers = True - option.view = True # opens the saved PDF in Acrobat. - pdf = os.path.join(mkdtemp(), "test.pdf") - ps.active_document.saveAs(pdf, option) +if __name__ == "__main__": + try: + logger.info("Starting PDF export test...") + + # Test simple PDF export + save_as_pdf_simple() + + # Test advanced PDF export + save_as_pdf_advanced() + + logger.info("PDF export test completed successfully") + + except Exception as e: + logger.error(f"PDF export test failed: {e!s}") + raise diff --git a/examples/save_as_tga.py b/examples/save_as_tga.py index 8eddf213..e99cdc79 100644 --- a/examples/save_as_tga.py +++ b/examples/save_as_tga.py @@ -1,23 +1,129 @@ +"""Save current active document as a TGA (Targa) file. + +This script demonstrates different ways to save a Photoshop document as TGA, +including various TGA options and settings. +""" + # Import built-in modules +from __future__ import annotations + +import logging import os from tempfile import mkdtemp # Import local modules from photoshop import Session +from photoshop.api.enumerations import LayerKind + +# Configure logging +logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s") +logger = logging.getLogger(__name__) + +def create_sample_document(ps, name="TGATest", width=500, height=500): + """Create a sample document with some content. + + Args: + ps: Photoshop session object + name (str): Document name + width (int): Document width + height (int): Document height + Returns: + Document: Created document + """ + # Create a new document + doc = ps.app.documents.add( + width=width, + height=height, + resolution=72, + name=name, + ) -with Session(action="new_document") as ps: - doc = ps.active_document + # Add a text layer + text_layer = doc.artLayers.add() + text_layer.kind = LayerKind.TextLayer + + # Set text color to green text_color = ps.SolidColor() text_color.rgb.green = 255 text_color.rgb.red = 0 text_color.rgb.blue = 0 - new_text_layer = doc.artLayers.add() - new_text_layer.kind = ps.LayerKind.TextLayer - new_text_layer.textItem.contents = "Hello, World!" - new_text_layer.textItem.position = [160, 167] - new_text_layer.textItem.size = 40 - new_text_layer.textItem.color = text_color - tga_file = os.path.join(mkdtemp("photoshop-python-api"), "test.tga") - doc.saveAs(tga_file, ps.TargaSaveOptions(), asCopy=True) - os.startfile(tga_file) + + # Configure text properties + text_layer.textItem.contents = "Hello, TGA!" + text_layer.textItem.position = [width//3, height//3] + text_layer.textItem.size = 40 + text_layer.textItem.color = text_color + + return doc + +def save_as_tga_simple(): + """Save document as TGA with basic settings.""" + try: + with Session(action="new_document") as ps: + # Create test document + doc = create_sample_document(ps) + + # Basic TGA options + options = ps.TargaSaveOptions() + options.alphaChannels = True # Include alpha channels + options.resolution = 32 # 32-bit depth + + # Save the file + tga_path = os.path.join(mkdtemp("photoshop-python-api"), "test_simple.tga") + doc.saveAs(tga_path, options, asCopy=True) + logger.info(f"TGA saved successfully: {tga_path}") + + # Open the file + os.startfile(tga_path) + + # Close the document + doc.close() + + except Exception as e: + logger.error(f"Failed to save TGA: {e!s}") + raise + +def save_as_tga_advanced(): + """Save document as TGA with advanced settings.""" + try: + with Session(action="new_document") as ps: + # Create test document with larger dimensions + doc = create_sample_document(ps, "TGATestAdvanced", 1000, 1000) + + # Advanced TGA options + options = ps.TargaSaveOptions() + options.alphaChannels = True # Include alpha channels + options.resolution = 32 # 32-bit depth + options.rleCompression = True # Use RLE compression + + # Save the file + tga_path = os.path.join(mkdtemp("photoshop-python-api"), "test_advanced.tga") + doc.saveAs(tga_path, options, asCopy=True) + logger.info(f"TGA saved successfully with advanced options: {tga_path}") + + # Open the file + os.startfile(tga_path) + + # Close the document + doc.close() + + except Exception as e: + logger.error(f"Failed to save TGA with advanced options: {e!s}") + raise + +if __name__ == "__main__": + try: + logger.info("Starting TGA export test...") + + # Test simple TGA export + save_as_tga_simple() + + # Test advanced TGA export + save_as_tga_advanced() + + logger.info("TGA export test completed successfully") + + except Exception as e: + logger.error(f"TGA export test failed: {e!s}") + raise diff --git a/examples/save_to_psd.py b/examples/save_to_psd.py index 6345c1ee..03e451af 100644 --- a/examples/save_to_psd.py +++ b/examples/save_to_psd.py @@ -1,13 +1,133 @@ -"""Save your current active document as a .psd file.""" +"""Example script demonstrating how to save Photoshop documents as PSD files. + +This script shows different methods for saving PSD files with various options: +1. Simple PSD saving with basic options +2. Advanced PSD saving with additional features like compatibility settings +""" + +# Import built-in modules +from __future__ import annotations + +import logging +import os +from tempfile import mkdtemp +from typing import Any + # Import local modules from photoshop import Session +# Configure logging +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s - %(levelname)s - %(message)s", +) +logger = logging.getLogger(__name__) + + +def create_sample_document( + ps: Any, + name: str = "PSDTest", + width: int = 500, + height: int = 500, +) -> Any: + """Create a sample document. + + Args: + ps: Photoshop session object. + name: Name for the new document. + width: Width of the document in pixels. + height: Height of the document in pixels. + + Returns: + Document: Created Photoshop document object. + """ + # Create a new document with high resolution + doc = ps.app.documents.add( + width=width, + height=height, + resolution=300, + name=name, + ) + + return doc + + +def save_as_psd_simple() -> None: + """Save document as PSD with basic settings.""" + try: + with Session() as ps: + # Create test document + doc = create_sample_document(ps) + + # Configure basic PSD options + options = ps.PhotoshopSaveOptions() + options.alphaChannels = True # Save alpha channels + options.layers = True # Save layers + options.spotColors = True # Save spot colors + options.embedColorProfile = True # Embed color profile + + # Save to temp directory + psd_path = os.path.join(mkdtemp(), "test_simple.psd") + doc.saveAs(psd_path, options, asCopy=True) + logger.info("PSD saved successfully: %s", psd_path) + + # Open the file for preview + os.startfile(psd_path) + + # Cleanup + doc.close() + + except Exception as e: + logger.error("Failed to save PSD: %s", str(e)) + raise + + +def save_as_psd_advanced() -> None: + """Save document as PSD with advanced settings.""" + try: + with Session() as ps: + # Create test document with moderate size + doc = create_sample_document(ps, "PSDTestAdvanced", 800, 800) + + # Configure advanced PSD options + options = ps.PhotoshopSaveOptions() + + # Layer settings + options.alphaChannels = True # Save alpha channels + options.layers = True # Save layers + options.spotColors = True # Save spot colors + options.embedColorProfile = True # Embed color profile + options.annotations = True # Save annotations + + # Compatibility settings + options.maximizeCompatibility = True # For better compatibility + + # Save to temp directory + psd_path = os.path.join(mkdtemp(), "test_advanced.psd") + doc.saveAs(psd_path, options, asCopy=True) + logger.info("PSD saved successfully with advanced options: %s", psd_path) + + # Open the file for preview + os.startfile(psd_path) + + # Cleanup + doc.close() + + except Exception as e: + logger.error("Failed to save PSD with advanced options: %s", str(e)) + raise + -with Session() as ps: - psd_file = "your/psd/save/file/path.psd" - doc = ps.active_document - options = ps.PhotoshopSaveOptions() - layers = doc.artLayers - doc.saveAs(psd_file, options, True) - ps.alert("Task done!") - ps.echo(doc.activeLayer) +if __name__ == "__main__": + try: + logger.info("Starting PSD export test...") + + # Test both PSD export methods + save_as_psd_simple() + save_as_psd_advanced() + + logger.info("PSD export test completed successfully") + + except Exception as e: + logger.error("PSD export test failed: %s", str(e)) + raise diff --git a/examples/selection_stroke.py b/examples/selection_stroke.py index ae2b5df4..e2d6b2cf 100644 --- a/examples/selection_stroke.py +++ b/examples/selection_stroke.py @@ -1,55 +1,166 @@ -"""Create a stroke around the current selection, Set the stroke color and -width of the new stroke. +"""Example script demonstrating how to create and stroke selections in Photoshop. + +This script shows how to: +1. Create a selection in a document +2. Apply a stroke with custom color and width +3. Handle different stroke locations and blend modes References: https://github.com/lohriialo/photoshop-scripting-python/blob/master/SelectionStroke.py - """ +# Import built-in modules +from __future__ import annotations + +import logging +from typing import Any, List, Tuple + # Import local modules +from photoshop import Session import photoshop.api as ps +# Configure logging +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s - %(levelname)s - %(message)s", +) +logger = logging.getLogger(__name__) + + +def create_selection( + doc: Any, + offset: int = 10, +) -> Tuple[List[Tuple[int, int]], Any]: + """Create a rectangular selection in the document. + + Args: + doc: Active Photoshop document. + offset: Offset from document edges in pixels. + + Returns: + Tuple containing: + - List of selection bounds points + - Selection object reference + """ + # Calculate selection bounds + bounds = [ + (offset, offset), + (doc.width - offset, offset), + (doc.width - offset, doc.height - offset), + (offset, doc.height - offset), + ] + + # Create selection + selection = doc.selection + selection.select(bounds) + + return bounds, selection + + +def create_stroke_color( + cyan: float = 58.0, + magenta: float = 0.0, + yellow: float = 70.0, + black: float = 0.0, +) -> Any: + """Create a CMYK color for the stroke. + + Args: + cyan: Cyan component (0-100). + magenta: Magenta component (0-100). + yellow: Yellow component (0-100). + black: Black component (0-100). + + Returns: + SolidColor object with specified CMYK values. + """ + color = ps.SolidColor() + color.cmyk.cyan = cyan + color.cmyk.magenta = magenta + color.cmyk.yellow = yellow + color.cmyk.black = black + return color + + +def apply_selection_stroke( + selection: Any, + color: Any, + width: int = 2, + location: Any = ps.StrokeLocation.OutsideStroke, + opacity: int = 75, +) -> None: + """Apply a stroke to the current selection. + + Args: + selection: Selection object to stroke. + color: Color to use for the stroke. + width: Width of the stroke in pixels. + location: Location of the stroke (inside, center, or outside). + opacity: Opacity of the stroke (0-100). + """ + selection.selectBorder(width) + selection.stroke( + color, + width, + location, + ps.ColorBlendMode.ColorBlendMode, + opacity, + True, + ) + + +def main() -> None: + """Main function to demonstrate selection stroking.""" + try: + with Session(action="new_document") as ps: + app = ps.app + + # Check if we're on background layer + doc = ps.active_document + if doc.activeLayer.isBackgroundLayer: + logger.error("Cannot perform operation on background layer") + return + + # Store original ruler units + start_ruler_units = app.preferences.rulerUnits + app.preferences.rulerUnits = ps.Units.Pixels + + try: + # Create selection + bounds, selection = create_selection(doc) + logger.info("Created selection at bounds: %s", bounds) + + # Create stroke color (lime green in CMYK) + stroke_color = create_stroke_color(58, 0, 70, 0) + + # Disable dialogs for automation + app.displayDialogs = ps.DialogModes.DisplayNoDialogs + + # Apply stroke + apply_selection_stroke( + selection, + stroke_color, + width=2, + location=ps.StrokeLocation.OutsideStroke, + opacity=75, + ) + logger.info("Applied stroke to selection") + + finally: + # Restore ruler units + app.preferences.rulerUnits = start_ruler_units + logger.info("Restored original ruler units") + + except Exception as e: + logger.error("Failed to stroke selection: %s", str(e)) + raise + -app = ps.Application() - -if len(list((i, x) for i, x in enumerate(app.documents, 1))) > 0: - if not app.activeDocument.activeLayer.isBackgroundLayer: - psPixels = 1 - start_ruler_units = app.Preferences.RulerUnits - app.preferences.rulerUnits = ps.Units.Pixels - - selRef = app.activeDocument.selection - offset = 10 - selBounds = ( - (offset, offset), - (app.activeDocument.width - offset, offset), - (app.activeDocument.width - offset, app.activeDocument.height - offset), - (offset, app.activeDocument.height - offset), - ) - - selRef.select(selBounds) - selRef.selectBorder(5) - - # create text color properties - strokeColor = ps.SolidColor() - - strokeColor.cmyk.cyan = 58 - strokeColor.cmyk.magenta = 0 - strokeColor.cmyk.yellow = 70 - strokeColor.cmyk.black = 0 - app.displayDialogs = ps.DialogModes.DisplayNoDialogs - selRef.stroke( - strokeColor, - 2, - ps.StrokeLocation.OutsideStroke, - ps.ColorBlendMode.ColorBlendMode, - 75, - True, - ) - - # Set ruler units back the way we found it. - app.preferences.rulerUnits = start_ruler_units - else: - print("Operation cannot be performed on background layer") -else: - print("Create a document with an active selection before running this " "script!") +if __name__ == "__main__": + try: + logger.info("Starting selection stroke example...") + main() + logger.info("Selection stroke example completed successfully") + except Exception as e: + logger.error("Selection stroke example failed: %s", str(e)) + raise diff --git a/examples/session_callback.py b/examples/session_callback.py index 076b1f77..42f280fe 100644 --- a/examples/session_callback.py +++ b/examples/session_callback.py @@ -1,12 +1,133 @@ +"""Example script demonstrating how to use session callbacks in Photoshop. + +This script shows how to: +1. Create and use session callbacks +2. Monitor document changes +3. Handle document events +4. Log document operations +""" + +# Import built-in modules +from __future__ import annotations + +import logging +from typing import Any + # Import local modules from photoshop import Session +import photoshop.api as ps + +# Configure logging +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s - %(levelname)s - %(message)s", +) +logger = logging.getLogger(__name__) + + +def log_document_info(ps_session: Any) -> None: + """Log information about the current document. + + Args: + ps_session: Active Photoshop session. + """ + try: + doc = ps_session.active_document + logger.info("Active document: %s", doc.name) + logger.info("Document size: %dx%d", doc.width, doc.height) + logger.info("Number of layers: %d", len(doc.layers)) + logger.info("Color mode: %s", doc.mode) + logger.info("Resolution: %d dpi", doc.resolution) + except Exception as e: + logger.error("Failed to log document info: %s", str(e)) + + +def create_test_document(ps_session: Any) -> None: + """Create a test document with some basic content. + + Args: + ps_session: Active Photoshop session. + """ + try: + app = ps_session.app + doc = app.documents.add( + width=800, + height=600, + resolution=300, + name="TestDocument", + ) + + # Add a text layer + text_layer = doc.artLayers.add() + text_layer.kind = ps.LayerKind.TextLayer + text_layer.textItem.contents = "Hello Photoshop!" + text_layer.textItem.position = [100, 100] + text_layer.textItem.size = 48 + + logger.info("Created test document with text layer") + + except Exception as e: + logger.error("Failed to create test document: %s", str(e)) + + +def monitor_document_changes(ps_session: Any) -> None: + """Monitor and log document changes. + + Args: + ps_session: Active Photoshop session. + """ + try: + doc = ps_session.active_document + logger.info("Document '%s' modified:", doc.name) + log_document_info(ps_session) + except Exception as e: + logger.error("Failed to monitor document changes: %s", str(e)) + + +def session_callback(ps_session: Any) -> None: + """Main session callback function. + + This function is called when the session is initialized. + It can be used to set up event handlers and perform initial tasks. + + Args: + ps_session: Active Photoshop session. + """ + try: + # Create a test document + create_test_document(ps_session) + + # Log initial document info + log_document_info(ps_session) + + # Show a message to the user + ps_session.echo("Session initialized successfully!") + ps_session.alert("Test document created. Check the console for details.") + + except Exception as e: + logger.error("Session callback failed: %s", str(e)) + ps_session.alert("Error in session callback. Check the console for details.") -def do_something(photoshop_api): - print(photoshop_api.active_document) - print("Do something.") +def main() -> None: + """Main function demonstrating session callback usage.""" + try: + # Initialize session with callback + with Session(callback=session_callback) as ps: + # The session_callback will be called automatically + # Additional operations can be performed here + monitor_document_changes(ps) + + except Exception as e: + logger.error("Session failed: %s", str(e)) + raise -with Session(callback=do_something) as ps: - ps.echo(ps.active_document.name) - ps.alert(ps.active_document.name) +if __name__ == "__main__": + try: + logger.info("Starting session callback example...") + main() + logger.info("Session callback example completed successfully") + except Exception as e: + logger.error("Session callback example failed: %s", str(e)) + raise diff --git a/examples/session_document_duplicate.py b/examples/session_document_duplicate.py index 3eb7c86e..4aecc722 100644 --- a/examples/session_document_duplicate.py +++ b/examples/session_document_duplicate.py @@ -1,7 +1,253 @@ -"""Action for duplicate current active document.""" +"""Example script demonstrating how to duplicate Photoshop documents. + +This script shows how to: +1. Create a sample document with content +2. Duplicate the document with different options +3. Modify the duplicate document +4. Handle document duplication errors +""" + +# Import built-in modules +from __future__ import annotations + +import logging +import os +from tempfile import mkdtemp +from typing import Any, Optional, Tuple + # Import local modules from photoshop import Session +import photoshop.api as ps + +# Configure logging +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s - %(levelname)s - %(message)s", +) +logger = logging.getLogger(__name__) + + +def create_sample_document( + ps_session: Any, + name: str = "OriginalDocument", + width: int = 800, + height: int = 600, +) -> Any: + """Create a sample document with some content. + + Args: + ps_session: Active Photoshop session. + name: Name for the new document. + width: Width of the document in pixels. + height: Height of the document in pixels. + + Returns: + Document object if successful, None otherwise. + """ + try: + # Create new document + doc = ps_session.app.documents.add( + width=width, + height=height, + resolution=300, + name=name, + ) + + # Add a text layer + text_layer = doc.artLayers.add() + text_layer.kind = ps.LayerKind.TextLayer + text_layer.textItem.contents = "Original Document" + text_layer.textItem.position = [50, 50] + text_layer.textItem.size = 36 + + # Add a solid color layer + color_layer = doc.artLayers.add() + color_layer.name = "Background Color" + + # Create and fill selection + doc.selection.select([ + [100, 100], + [300, 100], + [300, 300], + [100, 300], + ]) + + # Create a pink fill color + fill_color = ps.SolidColor() + fill_color.rgb.red = 255 + fill_color.rgb.green = 200 + fill_color.rgb.blue = 200 + + # Fill selection and deselect + doc.selection.fill(fill_color) + doc.selection.deselect() + + logger.info("Created sample document: %s", name) + return doc + + except Exception as e: + logger.error("Failed to create sample document: %s", str(e)) + return None + + +def duplicate_document( + doc: Any, + new_name: Optional[str] = None, + merge_layers: bool = False, +) -> Any: + """Duplicate a document with options. + + Args: + doc: Document to duplicate. + new_name: Name for the duplicate document. + merge_layers: Whether to merge layers in the duplicate. + + Returns: + Duplicated document object if successful, None otherwise. + """ + try: + # Generate new name if not provided + if not new_name: + new_name = f"{doc.name}_copy" + + # Duplicate document + duplicate = doc.duplicate(new_name) + logger.info("Duplicated document '%s' as '%s'", doc.name, new_name) + + # Merge layers if requested + if merge_layers and len(duplicate.layers) > 1: + duplicate.mergeVisibleLayers() + logger.info("Merged visible layers in duplicate document") + + return duplicate + + except Exception as e: + logger.error("Failed to duplicate document: %s", str(e)) + return None + + +def modify_duplicate(doc: Any) -> None: + """Make some modifications to the duplicated document. + + Args: + doc: Document to modify. + """ + try: + # Add a new text layer + text_layer = doc.artLayers.add() + text_layer.kind = ps.LayerKind.TextLayer + text_layer.textItem.contents = "Duplicate Document" + text_layer.textItem.position = [50, 150] + text_layer.textItem.size = 36 + + # Add a solid color layer + color_layer = doc.artLayers.add() + color_layer.name = "Additional Color" + + # Create and fill new selection + doc.selection.select([ + [400, 100], + [600, 100], + [600, 300], + [400, 300], + ]) + + # Create a light green fill color + fill_color = ps.SolidColor() + fill_color.rgb.red = 200 + fill_color.rgb.green = 255 + fill_color.rgb.blue = 200 + + # Fill selection and deselect + doc.selection.fill(fill_color) + doc.selection.deselect() + + logger.info("Modified duplicate document") + + except Exception as e: + logger.error("Failed to modify duplicate document: %s", str(e)) + + +def save_documents(original: Any, duplicate: Any) -> Tuple[str, str]: + """Save both original and duplicate documents. + + Args: + original: Original document object. + duplicate: Duplicate document object. + + Returns: + Tuple of (original_path, duplicate_path). + """ + try: + # Create temp directory for saving + temp_dir = mkdtemp() + + # Create PSD save options + save_options = ps.PhotoshopSaveOptions() + save_options.alphaChannels = True + save_options.layers = True + save_options.spotColors = True + save_options.embedColorProfile = True + + # Save original + original_path = os.path.join(temp_dir, f"{original.name}.psd") + original.saveAs(original_path, save_options, asCopy=True) + logger.info("Saved original document: %s", original_path) + + # Save duplicate + duplicate_path = os.path.join(temp_dir, f"{duplicate.name}.psd") + duplicate.saveAs(duplicate_path, save_options, asCopy=True) + logger.info("Saved duplicate document: %s", duplicate_path) + + return original_path, duplicate_path + + except Exception as e: + logger.error("Failed to save documents: %s", str(e)) + return "", "" + + +def main() -> None: + """Main function demonstrating document duplication.""" + try: + with Session(action="new_document") as ps: + # Create original document + original = create_sample_document(ps) + if not original: + return + + # Duplicate document + duplicate = duplicate_document( + original, + new_name="DuplicateDocument", + merge_layers=False, + ) + if not duplicate: + return + + # Modify duplicate + modify_duplicate(duplicate) + + # Save both documents + orig_path, dup_path = save_documents(original, duplicate) + + # Show results + if orig_path and dup_path: + ps.echo(f"Original document: {original.name}") + ps.echo(f"Duplicate document: {duplicate.name}") + ps.alert("Documents saved successfully. Check the console for paths.") + else: + ps.alert("Failed to save documents. Check the console for errors.") + + except Exception as e: + logger.error("Session failed: %s", str(e)) + raise -with Session(action="document_duplicate") as ps: - ps.echo(ps.active_document.name) +if __name__ == "__main__": + try: + logger.info("Starting document duplication example...") + main() + logger.info("Document duplication example completed successfully") + except Exception as e: + logger.error("Document duplication example failed: %s", str(e)) + raise diff --git a/examples/session_hello_world.py b/examples/session_hello_world.py deleted file mode 100644 index 308b3201..00000000 --- a/examples/session_hello_world.py +++ /dev/null @@ -1,24 +0,0 @@ -"""Add slate information dynamically.""" - -# Import built-in modules -import os -from tempfile import mkdtemp - -# Import local modules -from photoshop import Session - - -with Session() as adobe: - doc = adobe.app.documents.add(2000, 2000) - text_color = adobe.SolidColor() - text_color.rgb.red = 255 - new_text_layer = doc.artLayers.add() - new_text_layer.kind = adobe.LayerKind.TextLayer - new_text_layer.textItem.contents = "Hello, World!" - new_text_layer.textItem.position = [160, 167] - new_text_layer.textItem.size = 40 - new_text_layer.textItem.color = text_color - options = adobe.JPEGSaveOptions(quality=1) - jpg_file = os.path.join(mkdtemp("photoshop-python-api"), "hello_world.jpg") - doc.saveAs(jpg_file, options, asCopy=True) - adobe.app.doJavaScript(f'alert("save to jpg: {jpg_file}")') diff --git a/examples/session_new_document.py b/examples/session_new_document.py deleted file mode 100644 index 7e34c73f..00000000 --- a/examples/session_new_document.py +++ /dev/null @@ -1,7 +0,0 @@ -"""Action for create new document and print new document name.""" -# Import local modules -from photoshop import Session - - -with Session(action="new_document") as ps: - ps.echo(ps.active_document.name) diff --git a/examples/session_smart_sharpen.py b/examples/session_smart_sharpen.py deleted file mode 100644 index 0e2c29ab..00000000 --- a/examples/session_smart_sharpen.py +++ /dev/null @@ -1,53 +0,0 @@ -"""This script demonstrates how you can use the action manager to execute the -Emboss filter. - -References: - https://github.com/lohriialo/photoshop-scripting-python/blob/master/SmartSharpen.py - -""" - -# Import third-party modules -import examples._psd_files as psd # Import from examples. - -# Import local modules -from photoshop import Session - - -PSD_FILE = psd.get_psd_files() -file_path = PSD_FILE["layer_comps.psd"] - -with Session(file_path, action="open") as ps: - - def SmartSharpen(inAmount, inRadius, inNoise): - idsmart_sharpen_id = ps.app.stringIDToTypeID(ps.EventID.SmartSharpen) - desc37 = ps.ActionDescriptor() - - idpresetKind = ps.app.stringIDToTypeID(ps.EventID.PresetKind) - idpresetKindType = ps.app.stringIDToTypeID(ps.EventID.PresetKindType) - idpresetKindCustom = ps.app.stringIDToTypeID(ps.EventID.PresetKindCustom) - desc37.putEnumerated(idpresetKind, idpresetKindType, idpresetKindCustom) - idAmnt = ps.app.charIDToTypeID("Amnt") - idPrc = ps.app.charIDToTypeID("Rds ") - desc37.putUnitDouble(idAmnt, idPrc, inAmount) - - idRds = ps.app.charIDToTypeID("Rds ") - idPxl = ps.app.charIDToTypeID("#Pxl") - desc37.putUnitDouble(idRds, idPxl, inRadius) - - idnoiseReduction = ps.app.stringIDToTypeID("noiseReduction") - idPrc = ps.app.charIDToTypeID("#Prc") - desc37.putUnitDouble(idnoiseReduction, idPrc, inNoise) - - idblur = ps.app.charIDToTypeID("blur") - idblurType = ps.app.stringIDToTypeID("blurType") - idGsnB = ps.app.charIDToTypeID("GsnB") - desc37.putEnumerated(idblur, idblurType, idGsnB) - - ps.app.ExecuteAction(idsmart_sharpen_id, desc37) - - docRef = ps.active_document - nlayerSets = docRef.layerSets - nArtLayers = docRef.layerSets.item(nlayerSets.length) - docRef.activeLayer = nArtLayers.artLayers.item(nArtLayers.artLayers.length) - - SmartSharpen(300, 2.0, 20) diff --git a/examples/set_active_layer.py b/examples/set_active_layer.py index 8d676aed..b7463814 100644 --- a/examples/set_active_layer.py +++ b/examples/set_active_layer.py @@ -1,24 +1,187 @@ -""" +"""Example script demonstrating how to manage active layers in Photoshop. + +This script shows how to: +1. Create a document with multiple layers +2. Navigate between layers +3. Get and set the active layer +4. Modify layer properties +5. Handle layer selection errors + References: https://github.com/lohriialo/photoshop-scripting-python/blob/master/ActiveLayer.py - """ + +# Import built-in modules +from __future__ import annotations + +import logging +from typing import Any, List + # Import local modules +from photoshop import Session import photoshop.api as ps +# Configure logging +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s - %(levelname)s - %(message)s", +) +logger = logging.getLogger(__name__) + + +def create_sample_layers(doc: Any) -> List[Any]: + """Create sample layers in the document. + + Args: + doc: Active Photoshop document. + + Returns: + List of created layers. + """ + try: + layers = [] + + # Create text layers + for i in range(3): + layer = doc.artLayers.add() + layer.kind = ps.LayerKind.TextLayer + layer.name = f"Text Layer {i+1}" + layer.textItem.contents = f"Sample Text {i+1}" + layer.textItem.position = [50, 50 + i * 50] + layer.textItem.size = 24 + layers.append(layer) + logger.info("Created text layer: %s", layer.name) + + # Create shape layers + for i in range(2): + layer = doc.artLayers.add() + layer.name = f"Shape Layer {i+1}" + + # Create and fill selection + doc.selection.select([ + [200 + i * 150, 100], + [300 + i * 150, 100], + [300 + i * 150, 200], + [200 + i * 150, 200], + ]) + + # Create different colors for shapes + fill_color = ps.SolidColor() + if i == 0: + fill_color.rgb.red = 255 + fill_color.rgb.green = 200 + fill_color.rgb.blue = 200 + else: + fill_color.rgb.red = 200 + fill_color.rgb.green = 255 + fill_color.rgb.blue = 200 + + doc.selection.fill(fill_color) + doc.selection.deselect() + + layers.append(layer) + logger.info("Created shape layer: %s", layer.name) + + return layers + + except Exception as e: + logger.error("Failed to create sample layers: %s", str(e)) + return [] + + +def set_active_layer( + doc: Any, + target: Any, + by_name: bool = False, +) -> bool: + """Set the active layer in the document. + + Args: + doc: Active Photoshop document. + target: Layer object or layer name to activate. + by_name: If True, target is a layer name. If False, target is a layer object. + + Returns: + True if successful, False otherwise. + """ + try: + if by_name: + # Find layer by name + for i in range(doc.layers.length): + layer = doc.layers[i] + if layer.name == target: + doc.activeLayer = layer + logger.info("Activated layer by name: %s", target) + return True + logger.error("Layer not found: %s", target) + return False + # Set layer directly + doc.activeLayer = target + logger.info("Activated layer: %s", target.name) + return True + + except Exception as e: + logger.error("Failed to set active layer: %s", str(e)) + return False + + +def get_layer_info(layer: Any) -> None: + """Log information about a layer. + + Args: + layer: Photoshop layer object. + """ + try: + logger.info("Layer Info:") + logger.info(" Name: %s", layer.name) + logger.info(" Kind: %s", layer.kind) + logger.info(" Visible: %s", layer.visible) + logger.info(" Opacity: %d%%", layer.opacity) + + if layer.kind == ps.LayerKind.TextLayer: + logger.info(" Text: %s", layer.textItem.contents) + logger.info(" Font Size: %d", layer.textItem.size) + + except Exception as e: + logger.error("Failed to get layer info: %s", str(e)) + + +def main() -> None: + """Main function demonstrating layer management.""" + try: + with Session(action="new_document") as ps: + doc = ps.active_document + + # Create sample layers + layers = create_sample_layers(doc) + if not layers: + return + + # Show initial active layer + logger.info("Initial active layer: %s", doc.activeLayer.name) + + # Cycle through layers + for layer in layers: + if set_active_layer(doc, layer): + get_layer_info(layer) + + # Try to activate layer by name + if set_active_layer(doc, "Text Layer 1", by_name=True): + get_layer_info(doc.activeLayer) -app = ps.Application() + # Show final active layer + logger.info("Final active layer: %s", doc.activeLayer.name) -if app.documents.length < 1: - docRef = app.documents.add() -else: - docRef = app.activeDocument + except Exception as e: + logger.error("Session failed: %s", str(e)) + raise -if docRef.layers.length < 2: - docRef.artLayers.add() -activeLayerName = docRef.activeLayer.name -if docRef.activeLayer.name != docRef.layers.item(docRef.layers.length).name: - docRef.activeLayer = docRef.layers.item(docRef.layers.length) -else: - docRef.activeLayer = docRef.layers.item(1) +if __name__ == "__main__": + try: + logger.info("Starting layer management example...") + main() + logger.info("Layer management example completed successfully") + except Exception as e: + logger.error("Layer management example failed: %s", str(e)) + raise diff --git a/examples/smart_sharpen.py b/examples/smart_sharpen.py index 9fc3e5b3..db903917 100644 --- a/examples/smart_sharpen.py +++ b/examples/smart_sharpen.py @@ -1,56 +1,268 @@ -"""This script demonstrates how you can use the action manager to execute the -Emboss filter. +"""Example script demonstrating how to apply Smart Sharpen filter in Photoshop. + +This script shows how to: +1. Open an image and prepare it for sharpening +2. Apply Smart Sharpen filter with custom settings +3. Handle different blur types +4. Save the sharpened result +5. Support batch processing References: https://github.com/lohriialo/photoshop-scripting-python/blob/master/SmartSharpen.py - """ +# Import built-in modules +from __future__ import annotations + +import logging +import os +from tempfile import mkdtemp +from typing import Any, Dict, Optional, Tuple + # Import third-party modules -import examples._psd_files as psd # Import from examples. +import examples._psd_files as psd # Import local modules +from photoshop import Session import photoshop.api as ps +# Configure logging +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s - %(levelname)s - %(message)s", +) +logger = logging.getLogger(__name__) + + +def apply_smart_sharpen( + app: Any, + amount: float = 150.0, + radius: float = 1.0, + noise_reduction: float = 10.0, + blur_type: str = "gaussian", +) -> bool: + """Apply Smart Sharpen filter with specified settings. + + Args: + app: Photoshop application object. + amount: Sharpening amount (0-500). + radius: Sharpening radius in pixels (0.1-1000). + noise_reduction: Noise reduction percentage (0-100). + blur_type: Type of blur ('gaussian', 'lens', or 'motion'). + + Returns: + True if successful, False otherwise. + """ + try: + # Validate parameters + amount = max(0.0, min(500.0, amount)) + radius = max(0.1, min(1000.0, radius)) + noise_reduction = max(0.0, min(100.0, noise_reduction)) + + # Create descriptor + desc = ps.ActionDescriptor() + + # Set preset kind + preset_kind = app.stringIDToTypeID(ps.EventID.PresetKind) + preset_kind_type = app.stringIDToTypeID(ps.EventID.PresetKindType) + preset_kind_custom = app.stringIDToTypeID(ps.EventID.PresetKindCustom) + desc.putEnumerated(preset_kind, preset_kind_type, preset_kind_custom) + + # Set amount + amount_id = app.charIDToTypeID("Amnt") + percent_id = app.charIDToTypeID("#Prc") + desc.putUnitDouble(amount_id, percent_id, amount) + + # Set radius + radius_id = app.charIDToTypeID("Rds ") + pixel_id = app.charIDToTypeID("#Pxl") + desc.putUnitDouble(radius_id, pixel_id, radius) + + # Set noise reduction + noise_id = app.stringIDToTypeID("noiseReduction") + desc.putUnitDouble(noise_id, percent_id, noise_reduction) + + # Set blur type + blur_id = app.charIDToTypeID("blur") + blur_type_id = app.stringIDToTypeID("blurType") + blur_type_map = { + "gaussian": app.charIDToTypeID("GsnB"), + "lens": app.charIDToTypeID("LnsB"), + "motion": app.charIDToTypeID("MtnB"), + } + blur_type_value = blur_type_map.get(blur_type.lower(), blur_type_map["gaussian"]) + desc.putEnumerated(blur_id, blur_type_id, blur_type_value) + + # Execute filter + smart_sharpen_id = app.stringIDToTypeID(ps.EventID.SmartSharpen) + app.ExecuteAction(smart_sharpen_id, desc) + + logger.info( + "Applied Smart Sharpen (Amount: %.1f%%, Radius: %.1fpx, " + "Noise Reduction: %.1f%%, Blur: %s)", + amount, radius, noise_reduction, blur_type, + ) + return True + + except Exception as e: + logger.error("Failed to apply Smart Sharpen: %s", str(e)) + return False + + +def process_image( + doc: Any, + app: Any, + settings: Dict[str, float], +) -> bool: + """Process a single image with Smart Sharpen filter. + + Args: + doc: Document to process. + app: Photoshop application object. + settings: Dictionary of filter settings. + + Returns: + True if successful, False otherwise. + """ + try: + # Ensure we have a valid document + if not doc: + logger.error("No document provided") + return False -app = ps.Application() + # Log document info + logger.info("Processing document: %s", doc.name) + logger.info("Document size: %dx%d", doc.width, doc.height) + logger.info("Number of layers: %d", doc.artLayers.length) -PSD_FILE = psd.get_psd_files() -file_path = PSD_FILE["layer_comps.psd"] -docRef = app.open(file_path) + # Try to find a suitable layer to sharpen + if doc.artLayers.length > 0: + # Try to find a normal layer + found_layer = False + for i in range(doc.artLayers.length): + try: + layer = doc.artLayers[i] + layer_name = layer.name if hasattr(layer, "name") else f"Layer {i}" + layer_kind = layer.kind if hasattr(layer, "kind") else "Unknown" + + logger.info( + "Checking layer %d: %s (Kind: %s)", + i, layer_name, layer_kind, + ) + + # Try to make this layer active + doc.activeLayer = layer + found_layer = True + logger.info("Using layer for sharpening: %s", layer_name) + break + except Exception as e: + logger.warning("Failed to check layer %d: %s", i, str(e)) + continue + + if not found_layer: + logger.error("Could not find a suitable layer for sharpening") + return False + else: + logger.error("Document has no layers") + return False + + # Apply filter + return apply_smart_sharpen(app, **settings) -nlayerSets = docRef.layerSets -nArtLayers = docRef.layerSets.item(nlayerSets.length) -docRef.activeLayer = nArtLayers.artLayers.item(nArtLayers.artLayers.length) + except Exception as e: + logger.error("Failed to process image: %s", str(e)) + return False -def SmartSharpen(inAmount, inRadius, inNoise): - idsmart_sharpen_id = app.stringIDToTypeID(ps.EventID.SmartSharpen) - desc37 = ps.ActionDescriptor() +def save_result( + doc: Any, + output_dir: Optional[str] = None, + format_options: Optional[Dict] = None, +) -> Tuple[bool, str]: + """Save the processed document. - idpresetKind = app.stringIDToTypeID(ps.EventID.PresetKind) - idpresetKindType = app.stringIDToTypeID(ps.EventID.PresetKindType) - idpresetKindCustom = app.stringIDToTypeID(ps.EventID.PresetKindCustom) - desc37.putEnumerated(idpresetKind, idpresetKindType, idpresetKindCustom) + Args: + doc: Document to save. + output_dir: Directory to save the file in. + format_options: Dictionary of save options. - idAmnt = app.charIDToTypeID("Amnt") - idPrc = app.charIDToTypeID("Rds ") - desc37.putUnitDouble(idAmnt, idPrc, inAmount) + Returns: + Tuple of (success, output_path). + """ + try: + # Setup save location + if not output_dir: + output_dir = mkdtemp() + + # Setup format options + if not format_options: + format_options = { + "format": "psd", + "options": ps.PhotoshopSaveOptions(), + } + + # Generate output path + base_name = os.path.splitext(os.path.basename(doc.name))[0] + output_path = os.path.join( + output_dir, + f"{base_name}_sharpened.{format_options['format']}", + ) + + # Save document + doc.saveAs(output_path, format_options["options"]) + logger.info("Saved result to: %s", output_path) + + return True, output_path - idRds = app.charIDToTypeID("Rds ") - idPxl = app.charIDToTypeID("#Pxl") - desc37.putUnitDouble(idRds, idPxl, inRadius) + except Exception as e: + logger.error("Failed to save result: %s", str(e)) + return False, "" - idnoiseReduction = app.stringIDToTypeID("noiseReduction") - idPrc = app.charIDToTypeID("#Prc") - desc37.putUnitDouble(idnoiseReduction, idPrc, inNoise) - idblur = app.charIDToTypeID("blur") - idblurType = app.stringIDToTypeID("blurType") - idGsnB = app.charIDToTypeID("GsnB") - desc37.putEnumerated(idblur, idblurType, idGsnB) +def main() -> None: + """Main function demonstrating Smart Sharpen filter usage.""" + try: + with Session() as ps_app: + # Get sample PSD file + psd_files = psd.get_psd_files() + file_path = psd_files["trim.psd"] # Using a simpler file + + # Open document + doc = ps_app.app.open(file_path) + logger.info("Opened document: %s", doc.name) + + # Define filter settings + settings = { + "amount": 150.0, + "radius": 1.0, + "noise_reduction": 10.0, + "blur_type": "gaussian", + } + + # Process image + if process_image(doc, ps_app.app, settings): + # Save result + success, output_path = save_result(doc) + if success: + ps_app.alert( + "Smart Sharpen applied successfully!\n" + f"Result saved to: {output_path}", + ) + else: + ps_app.alert("Failed to save the result!") + else: + ps_app.alert("Failed to apply Smart Sharpen!") - app.ExecuteAction(idsmart_sharpen_id, desc37) + except Exception as e: + logger.error("Session failed: %s", str(e)) + raise -SmartSharpen(300, 2.0, 20) +if __name__ == "__main__": + try: + logger.info("Starting Smart Sharpen example...") + main() + logger.info("Smart Sharpen example completed successfully") + except Exception as e: + logger.error("Smart Sharpen example failed: %s", str(e)) + raise diff --git a/examples/test_rotate_layer.py b/examples/test_rotate_layer.py new file mode 100644 index 00000000..fa88831f --- /dev/null +++ b/examples/test_rotate_layer.py @@ -0,0 +1,83 @@ +"""Test the layer rotation functionality. + +This script demonstrates how to use the rotate function to rotate layers in Photoshop. +""" + +# Import built-in modules +import os +import sys +import logging +from pathlib import Path + +# Import local modules +sys.path.append(os.path.dirname(os.path.dirname(__file__))) +from photoshop import Session +from photoshop.api.errors import PhotoshopPythonAPIError +from photoshop.api.enumerations import SaveOptions + +# Configure logging +logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s") +logger = logging.getLogger(__name__) + +def test_layer_rotation(): + """Test rotating layers with different angles.""" + try: + # Start a new Photoshop session + with Session() as ps: + # Create a new document + doc = ps.app.documents.add( + width=500, + height=500, + resolution=72, + name="RotationTest", + ) + + # Create a new layer + layer = doc.artLayers.add() + layer.name = "TestLayer" + + # Draw something on the layer for visibility + # Use a rectangle for testing + selection = ps.active_document.selection + selection.select([[100, 100], [400, 100], [400, 400], [100, 400]]) + ps.app.foregroundColor.rgb.red = 255 + ps.app.foregroundColor.rgb.green = 0 + ps.app.foregroundColor.rgb.blue = 0 + selection.fill(ps.app.foregroundColor) + selection.deselect() + + # Test different rotation angles + test_angles = [45, 90, 180, -90, -45] + + for angle in test_angles: + try: + logger.info(f"Testing rotation with angle: {angle}") + layer.rotate(angle) + # Add a small delay to make the rotation visible + ps.app.refresh() + except PhotoshopPythonAPIError as e: + logger.error(f"Failed to rotate layer by {angle} degrees: {e!s}") + continue + + # Save the test document using JavaScript + save_path = str(Path(__file__).parent / "rotation_test.psd") + save_js = ( + 'try {' + f' var saveFile = new File("{save_path.replace(os.sep, "/")}");' + ' app.activeDocument.saveAs(saveFile);' + '} catch(e) {' + ' throw e;' + '}' + ) + ps.app.doJavaScript(save_js) + logger.info(f"Test document saved to: {save_path}") + + # Close the document + doc.close(SaveOptions.DoNotSaveChanges) + + except Exception as e: + logger.error(f"Test failed with error: {e!s}") + raise + +if __name__ == "__main__": + test_layer_rotation() diff --git a/examples/toggle_proof_colors.py b/examples/toggle_proof_colors.py index e62cc833..0d98507b 100644 --- a/examples/toggle_proof_colors.py +++ b/examples/toggle_proof_colors.py @@ -1,12 +1,46 @@ -"""Toggle the proof color. +"""Toggle the proof color in Photoshop. -Like operating in the menu: -**View** > **Proof Colors** (Ctrl + Y) +This script demonstrates how to toggle the proof colors in Photoshop, +which is equivalent to using the menu option View > Proof Colors (Ctrl + Y). +The proof colors feature allows you to preview how your document will look +when output on different devices or in different color spaces. + +Example: + Run this script directly to toggle proof colors: + $ python toggle_proof_colors.py """ + +# Import built-in modules +from __future__ import annotations +import logging +from typing import NoReturn + # Import local modules from photoshop import Session +# Setup logging +logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s") +logger = logging.getLogger(__name__) + + +def toggle_proof_colors() -> NoReturn: + """Toggle the proof colors in Photoshop. + + This function uses the Photoshop COM interface to toggle the proof colors view, + which is equivalent to pressing Ctrl+Y in Photoshop. + + Raises: + Exception: If there's an error toggling the proof colors. + """ + try: + with Session() as ps: + ps.app.runMenuItem(ps.app.stringIDToTypeID("toggleProofColors")) + logger.info("Successfully toggled proof colors") + except Exception as e: + logger.error("Failed to toggle proof colors: %s", str(e)) + raise + -with Session() as ps: - ps.app.runMenuItem(ps.app.stringIDToTypeID("toggleProofColors")) +if __name__ == "__main__": + toggle_proof_colors() diff --git a/examples/trim.py b/examples/trim.py index d9e7c116..52dd8b42 100644 --- a/examples/trim.py +++ b/examples/trim.py @@ -1,13 +1,73 @@ -"""A trim example.""" +"""Demonstrate how to trim a Photoshop document. + +This script shows how to use the trim operation in Photoshop, which removes +transparent or solid-colored pixels from the edges of an image. This is +equivalent to using Image > Trim in Photoshop. + +The trim operation can remove: + - Transparent pixels + - Top-left pixel color + - Bottom-right pixel color + +Example: + Run this script directly to trim the example PSD file: + $ python trim.py +""" + +# Import built-in modules +from __future__ import annotations +import logging +from typing import NoReturn # Import third-party modules -import examples._psd_files as psd # Import from examples. +import examples._psd_files as psd # Import local modules from photoshop import Session +# Setup logging +logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s") +logger = logging.getLogger(__name__) + + +def trim_document(psd_file: str) -> NoReturn: + """Trim the transparent pixels from edges of the document. + + This function opens a PSD file and trims transparent pixels from all edges + (top, left, bottom, right) using the top-left pixel as the base color. + + Args: + psd_file: Path to the PSD file to trim. + + Raises: + Exception: If there's an error during the trim operation. + """ + try: + with Session(psd_file, action="open") as ps: + # Trim transparent pixels from all edges using top-left pixel color + ps.active_document.trim( + ps.TrimType.TopLeftPixel, # Use top-left pixel color as base + True, # Trim top + True, # Trim left + True, # Trim bottom + True, # Trim right + ) + logger.info("Successfully trimmed document: %s", psd_file) + except Exception as e: + logger.error("Failed to trim document: %s", str(e)) + raise + + +def main() -> NoReturn: + """Main function to demonstrate the trim operation.""" + try: + psd_files = psd.get_psd_files() + example_file = psd_files["trim.psd"] + trim_document(example_file) + except Exception as e: + logger.error("Failed to process file: %s", str(e)) + raise + -PSD_FILE = psd.get_psd_files() -example_file = PSD_FILE["trim.psd"] -with Session(example_file, action="open") as ps: - ps.active_document.trim(ps.TrimType.TopLeftPixel, True, True, True, True) +if __name__ == "__main__": + main() diff --git a/mkdocs.yml b/mkdocs.yml index db6b67c1..cb304692 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -4,24 +4,38 @@ repo_name: github site_author: longhao remote_branch: master copyright: "Copyright (c) 2019 Long Hao" -features: - - content.code.annotate - - navigation.indexes - - navigation.sections - - navigation.tabs - - navigation.top - - navigation.tracking - - search.highlight - - search.share - - search.suggest - - toc.follow -docs_dir: docs +theme: + name: material + features: + - content.code.annotate + - navigation.indexes + - navigation.sections + - navigation.tabs + - navigation.top + - navigation.tracking + - search.highlight + - search.share + - search.suggest + - toc.follow + palette: + - media: "(prefers-color-scheme: light)" + scheme: default + primary: deep purple + accent: deep purple + toggle: + icon: material/brightness-7 + name: Switch to dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + primary: deep purple + accent: deep purple + toggle: + icon: material/brightness-4 + name: Switch to light mode -palette: - scheme: dracula - primary: deep purple # Primary colors - accent: deep purple # accent color +docs_dir: docs +site_dir: site nav: - Home: @@ -32,7 +46,6 @@ nav: - Code Reference: reference/ - 'Issue Tracker': 'https://github.com/loonghao/photoshop-python-api/issues' - markdown_extensions: - pymdownx.extra - admonition @@ -48,27 +61,8 @@ markdown_extensions: - pymdownx.snippets - pymdownx.highlight - pymdownx.emoji: - emoji_index: !!python/name:materialx.emoji.twemoji - emoji_generator: !!python/name:materialx.emoji.to_svg - - pymdownx.inlinehilite - - pymdownx.magiclink - - pymdownx.mark - - pymdownx.smartsymbols - - pymdownx.tabbed: - alternate_style: true - - pymdownx.superfences: - custom_fences: - - name: mermaid - class: mermaid - format: !!python/name:pymdownx.superfences.fence_code_format - - pymdownx.tasklist: - custom_checkbox: true - - pymdownx.tilde - - attr_list - - md_in_html - -theme: - name: material + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg plugins: - include-markdown diff --git a/noxfile.py b/noxfile.py new file mode 100644 index 00000000..feda9116 --- /dev/null +++ b/noxfile.py @@ -0,0 +1,247 @@ +"""Nox sessions.""" +from __future__ import annotations + +import os +import shutil +import sys +from pathlib import Path +from textwrap import dedent + +import nox +from nox.sessions import Session + +PYTHON_VERSIONS = ["3.8", "3.9", "3.10", "3.11", "3.12"] +nox.needs_version = ">= 2023.4.22" +nox.options.sessions = ( + "pre-commit", + "ruff", + "safety", + "mypy", + "tests", + "docs-build", + "build", + "release", +) + + +def install_with_uv(session: Session, *args: str) -> None: + """Install packages with uv.""" + session.install("uv") + if args: + session.run("uv", "pip", "install", *args, external=True) + + +def activate_virtualenv_in_precommit_hooks(session: Session) -> None: + """Activate virtualenv in hooks installed by pre-commit.""" + assert session.bin is not None # noqa: S101 + + virtualenv = session.env.get("VIRTUAL_ENV") + if virtualenv is None: + return + + hookdir = Path(".git") / "hooks" + if not hookdir.is_dir(): + return + + for hook in hookdir.iterdir(): + if hook.name.endswith(".sample") or not hook.is_file(): + continue + + text = hook.read_text() + bindir = repr(session.bin)[1:-1] # strip quotes + if not ( + (Path("A") == Path("a") and bindir.lower() in text.lower()) or bindir in text + ): + continue + + lines = text.splitlines() + if not (lines[0].startswith("#!") and "python" in lines[0].lower()): + continue + + header = dedent( + f"""\ + import os + os.environ["VIRTUAL_ENV"] = {virtualenv!r} + os.environ["PATH"] = os.pathsep.join(( + {session.bin!r}, + os.environ.get("PATH", ""), + )) + """, + ) + + lines.insert(1, header) + hook.write_text("\n".join(lines)) + + +@nox.session(name="pre-commit") +def precommit(session: Session) -> None: + """Lint using pre-commit.""" + args = session.posargs or ["run", "--all-files", "--show-diff-on-failure"] + install_with_uv( + session, + "black", + "flake8", + "flake8-bandit", + "flake8-bugbear", + "flake8-docstrings", + "flake8-rst-docstrings", + "isort", + "pep8-naming", + "pre-commit", + "pre-commit-hooks", + "pyupgrade", + "ruff", + ) + session.run("pre-commit", *args) + if args and args[0] == "install": + activate_virtualenv_in_precommit_hooks(session) + + +@nox.session() +def ruff(session: Session) -> None: + """Run ruff.""" + args = session.posargs or ["check", ".", "--fix"] + install_with_uv(session, "ruff") + session.run("ruff", *args) + + +@nox.session() +def safety(session: Session) -> None: + """Scan dependencies for insecure packages.""" + with open("requirements.txt") as f: + requirements = f.read() + install_with_uv(session, "safety") + session.run("safety", "check", "--full-report", requirements) + + +@nox.session() +def mypy(session: Session) -> None: + """Type-check using mypy.""" + args = session.posargs or ["photoshop"] + install_with_uv( + session, + ".", + "mypy", + "types-setuptools", + "types-requests", + "types-six", + "types-python-dateutil", + ) + session.run("mypy", *args) + if not session.posargs: + session.run("mypy", f"--python-executable={sys.executable}", "noxfile.py") + + +@nox.session(python=PYTHON_VERSIONS) +def tests(session: Session) -> None: + """Run the test suite.""" + install_with_uv(session, ".", "coverage[toml]", "pytest", "pygments", "pytest-cov") + try: + session.run("coverage", "run", "--parallel", "-m", "pytest", *session.posargs) + finally: + if session.interactive: + session.notify("coverage", posargs=[]) + + +@nox.session() +def coverage(session: Session) -> None: + """Produce the coverage report.""" + install_with_uv(session, "coverage[toml]") + + if not session.posargs and any(Path().glob(".coverage.*")): + session.run("coverage", "combine") + + session.run("coverage", "report") + + +@nox.session(name="docs-build") +def docs_build(session: Session) -> None: + """Build the documentation.""" + args = session.posargs or ["docs", "build"] + install_with_uv( + session, + ".", + "mkdocs", + "mkdocs-git-revision-date-plugin", + "mkdocs-material", + "mkdocstrings-python", + "mkdocs-pymdownx-material-extras", + "mkdocs-same-dir", + "mkdocs-include-markdown-plugin", + "mkdocs-gen-files", + "mkdocs-literate-nav", + "mkdocs-git-revision-date-localized-plugin", + "mkdocs-section-index", + "mkdocs-git-authors-plugin", + "mkdocs-autolinks-plugin", + "mkdocs-minify-plugin", + "stringcase", + ) + + build_dir = Path("site") + if build_dir.exists(): + shutil.rmtree(build_dir) + + session.run("mkdocs", "build") + + +@nox.session() +def docs(session: Session) -> None: + """Build and serve the documentation with live reloading on file changes.""" + args = session.posargs or ["serve"] + install_with_uv( + session, + ".", + "mkdocs", + "mkdocs-git-revision-date-plugin", + "mkdocs-material", + "mkdocstrings-python", + "mkdocs-pymdownx-material-extras", + "mkdocs-same-dir", + "mkdocs-include-markdown-plugin", + "mkdocs-gen-files", + "mkdocs-literate-nav", + "mkdocs-git-revision-date-localized-plugin", + "mkdocs-section-index", + "mkdocs-git-authors-plugin", + "mkdocs-autolinks-plugin", + "mkdocs-minify-plugin", + "stringcase", + ) + + if args[0] == "serve": + session.run("mkdocs", "serve") + elif args[0] == "build": + session.run("mkdocs", "build") + elif args[0] == "deploy": + session.run("mkdocs", "gh-deploy", "--force") + else: + session.run("mkdocs", *args) + + +@nox.session() +def build(session: Session) -> None: + """Build the package.""" + install_with_uv(session, "build") + session.run("python", "-m", "build") + + +@nox.session() +def release(session: Session) -> None: + """Release the package to PyPI.""" + if not session.posargs: + session.error("Please provide a version number, e.g: nox -s release -- 1.0.0") + + # Install dependencies + install_with_uv(session, "twine", "build") + + # Clean up previous builds + if os.path.exists("dist"): + shutil.rmtree("dist") + + # Build the package + session.run("python", "-m", "build") + + # Upload to PyPI + session.run("twine", "check", "dist/*") + session.run("twine", "upload", "dist/*") diff --git a/photoshop/__init__.py b/photoshop/__init__.py index 4388f60a..8ef634e4 100644 --- a/photoshop/__init__.py +++ b/photoshop/__init__.py @@ -1,5 +1,6 @@ -# Import local modules -from photoshop.session import Session - - -__all__ = ["Session"] +# Import local modules +from __future__ import annotations + +from photoshop.session import Session + +__all__ = ["Session"] diff --git a/photoshop/__version__.py b/photoshop/__version__.py index f5c474ba..bbe83b34 100644 --- a/photoshop/__version__.py +++ b/photoshop/__version__.py @@ -1 +1,3 @@ -__version__ = "0.22.10" +from __future__ import annotations + +__version__ = "0.22.10" diff --git a/photoshop/api/__init__.py b/photoshop/api/__init__.py index 4bfddeb3..3670838e 100644 --- a/photoshop/api/__init__.py +++ b/photoshop/api/__init__.py @@ -1,62 +1,60 @@ -"""Python API for Photoshop.""" -# Import local modules -from photoshop.api import constants -from photoshop.api.action_descriptor import ActionDescriptor -from photoshop.api.action_list import ActionList -from photoshop.api.action_reference import ActionReference -from photoshop.api.application import Application -from photoshop.api.batch_options import BatchOptions -from photoshop.api.colors import CMYKColor -from photoshop.api.colors import GrayColor -from photoshop.api.colors import HSBColor -from photoshop.api.colors import LabColor -from photoshop.api.colors import RGBColor -from photoshop.api.enumerations import * # noqa: F403 -from photoshop.api.errors import PhotoshopPythonAPICOMError -from photoshop.api.errors import PhotoshopPythonAPIError -from photoshop.api.event_id import EventID -from photoshop.api.open_options import EPSOpenOptions -from photoshop.api.save_options import BMPSaveOptions -from photoshop.api.save_options import EPSSaveOptions -from photoshop.api.save_options import ExportOptionsSaveForWeb -from photoshop.api.save_options import GIFSaveOptions -from photoshop.api.save_options import JPEGSaveOptions -from photoshop.api.save_options import PDFSaveOptions -from photoshop.api.save_options import PNGSaveOptions -from photoshop.api.save_options import PhotoshopSaveOptions -from photoshop.api.save_options import TargaSaveOptions -from photoshop.api.save_options import TiffSaveOptions -from photoshop.api.solid_color import SolidColor -from photoshop.api.text_item import TextItem - - -__all__ = [ # noqa: F405 - "ActionDescriptor", - "ActionReference", - "ActionList", - "Application", - "BatchOptions", - "constants", - "enumerations", - "PhotoshopPythonAPIError", - "PhotoshopPythonAPICOMError", - "CMYKColor", - "GrayColor", - "HSBColor", - "LabColor", - "RGBColor", - "SolidColor", - "EventID", - "BMPSaveOptions", - "GIFSaveOptions", - "JPEGSaveOptions", - "PDFSaveOptions", - "ExportOptionsSaveForWeb", - "PNGSaveOptions", - "PhotoshopSaveOptions", - "TiffSaveOptions", - "TargaSaveOptions", - "EPSOpenOptions", - "EPSSaveOptions", - "TextItem", -] +"""Python API for Photoshop.""" +# Import local modules +from __future__ import annotations + +from photoshop.api import constants +from photoshop.api.action_descriptor import ActionDescriptor +from photoshop.api.action_list import ActionList +from photoshop.api.action_reference import ActionReference +from photoshop.api.application import Application +from photoshop.api.batch_options import BatchOptions +from photoshop.api.colors import CMYKColor, GrayColor, HSBColor, LabColor, RGBColor +from photoshop.api.enumerations import * # noqa: F403 +from photoshop.api.errors import PhotoshopPythonAPICOMError, PhotoshopPythonAPIError +from photoshop.api.event_id import EventID +from photoshop.api.open_options import EPSOpenOptions +from photoshop.api.save_options import ( + BMPSaveOptions, + EPSSaveOptions, + ExportOptionsSaveForWeb, + GIFSaveOptions, + JPEGSaveOptions, + PDFSaveOptions, + PhotoshopSaveOptions, + PNGSaveOptions, + TargaSaveOptions, + TiffSaveOptions, +) +from photoshop.api.solid_color import SolidColor +from photoshop.api.text_item import TextItem + +__all__ = [ # noqa: F405 + "ActionDescriptor", + "ActionList", + "ActionReference", + "Application", + "BMPSaveOptions", + "BatchOptions", + "CMYKColor", + "EPSOpenOptions", + "EPSSaveOptions", + "EventID", + "ExportOptionsSaveForWeb", + "GIFSaveOptions", + "GrayColor", + "HSBColor", + "JPEGSaveOptions", + "LabColor", + "PDFSaveOptions", + "PNGSaveOptions", + "PhotoshopPythonAPICOMError", + "PhotoshopPythonAPIError", + "PhotoshopSaveOptions", + "RGBColor", + "SolidColor", + "TargaSaveOptions", + "TextItem", + "TiffSaveOptions", + "constants", + "enumerations", +] diff --git a/photoshop/api/_active_layer.py b/photoshop/api/_active_layer.py index 16449766..da330457 100644 --- a/photoshop/api/_active_layer.py +++ b/photoshop/api/_active_layer.py @@ -1,18 +1,20 @@ -# 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() +# Import local modules +from __future__ import annotations + +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 ae1b84ec..ede2fbec 100644 --- a/photoshop/api/_artlayer.py +++ b/photoshop/api/_artlayer.py @@ -1,11 +1,13 @@ # Import built-in modules +from __future__ import annotations + from typing import Any # Import local modules from photoshop.api._core import Photoshop -from photoshop.api.enumerations import LayerKind -from photoshop.api.enumerations import RasterizeType +from photoshop.api.enumerations import LayerKind, RasterizeType from photoshop.api.text_item import TextItem +from photoshop.api.errors import PhotoshopPythonAPIError # pylint: disable=too-many-public-methods, too-many-arguments @@ -57,6 +59,7 @@ def __init__(self, parent: Any = None): "move", "posterize", "rasterize", + "rotate", "unlink", ) @@ -86,7 +89,9 @@ def linkedLayers(self) -> list: """Get all layers linked to this layer. Returns: - list: Layer objects""" + list: Layer objects + + """ return [ArtLayer(layer) for layer in self.app.linkedLayers] @property @@ -155,7 +160,7 @@ def kind(self): @kind.setter def kind(self, layer_type): - """set the layer kind.""" + """Set the layer kind.""" self.app.kind = layer_type @property @@ -207,7 +212,8 @@ def pixelsLocked(self, value): @property def positionLocked(self): """bool: If true, the pixels in the layer’s image cannot be moved - within the layer.""" + within the layer. + """ return self.app.positionLocked @positionLocked.setter @@ -219,8 +225,9 @@ def textItem(self) -> TextItem: """The text that is associated with the layer. Valid only when ‘kind’ is text layer. - returns: + Returns: TextItem: + """ return TextItem(self.app.textItem) @@ -279,83 +286,84 @@ def adjustBrightnessContrast(self, brightness, contrast): def adjustColorBalance( self, - shadows, - midtones, - highlights, - preserveLuminosity, - ): - """Adjusts the color balance of the layer’s component channels. + shadows: list = None, + midtones: list = None, + highlights: list = None, + preserve_luminosity: bool = True, + ) -> None: + """Apply the color balance adjustment to the layer. Args: - shadows: The adjustments for the shadows. The array must include - three values (in the range -100 to 100), which represent - cyan or red, magenta or green, and yellow or blue, when - the document mode is CMYK or RGB. - midtones: The adjustments for the midtones. The array must include - three values (in the range -100 to 100), which represent - cyan or red, magenta or green, and yellow or blue, when - the document mode is CMYK or RGB. - highlights: The adjustments for the highlights. The array must - include three values (in the range -100 to 100), which - represent cyan or red, magenta or green, and yellow or - blue, when the document mode is CMYK or RGB. - preserveLuminosity: If true, luminosity is preserved. + shadows: Shadows color levels adjustment. + midtones: Midtones color levels adjustment. + highlights: Highlights color levels adjustment. + preserve_luminosity: Option to preserve luminosity. """ return self.app.adjustColorBalance( shadows, midtones, highlights, - preserveLuminosity, + preserve_luminosity, ) - def adjustCurves(self, curveShape): - """Adjusts the tonal range of the selected channel using up to fourteen - points. - - + def adjustCurves(self, curve_shape: list) -> None: + """Apply a curves adjustment to the layer. Args: - curveShape: The curve points. The number of points must be between - 2 and 14. - - Returns: + curve_shape: List of curve adjustment points. """ - return self.app.adjustCurves(curveShape) + return self.app.adjustCurves(curve_shape) def adjustLevels( self, - inputRangeStart, - inputRangeEnd, - inputRangeGamma, - outputRangeStart, - outputRangeEnd, - ): - """Adjusts levels of the selected channels. + input_range_start: int = 0, + input_range_end: int = 255, + input_range_gamma: float = 1.0, + output_range_start: int = 0, + output_range_end: int = 255, + ) -> None: + """Apply a levels adjustment to the layer. Args: - inputRangeStart: - inputRangeEnd: - inputRangeGamma: - outputRangeStart: - outputRangeEnd: - - Returns: + input_range_start: Start of input range (0-255). + input_range_end: End of input range (0-255). + input_range_gamma: Gamma value for input range (typically 0.1-10). + output_range_start: Start of output range (0-255). + output_range_end: End of output range (0-255). """ return self.app.adjustLevels( - inputRangeStart, - inputRangeEnd, - inputRangeGamma, - outputRangeStart, - outputRangeEnd, + input_range_start, + input_range_end, + input_range_gamma, + output_range_start, + output_range_end, ) - def applyAddNoise(self, amount, distribution, monochromatic): - return self.app.applyAddNoise(amount, distribution, monochromatic) + def applyAddNoise( + self, + graininess: float, + amount: float, + clear_amount: float, + ) -> None: + """Add noise to the layer. + + Args: + graininess: Graininess level (0.0-100.0). + amount: Amount of noise to add (0.0-100.0). + clear_amount: Clear amount value (0.0-100.0). - def applyDiffuseGlow(self, graininess, amount, clear_amount): + """ + return self.app.applyAddNoise(graininess, amount, clear_amount) + + def applyDiffuseGlow( + self, + graininess: float, + amount: float, + clear_amount: float, + ) -> None: """Applies the diffuse glow filter. Args: @@ -532,3 +540,34 @@ def invert(self): def duplicate(self, relativeObject=None, insertionLocation=None): return ArtLayer(self.app.duplicate(relativeObject, insertionLocation)) + + def rotate(self, angle: float) -> None: + """Rotate the layer by a specified angle. + + Args: + angle (float): The angle to rotate in degrees. Positive values rotate clockwise, + negative values rotate counterclockwise. + + Returns: + None + + Raises: + PhotoshopPythonAPIError: If the rotation operation fails. + """ + # Create a JavaScript code to perform the rotation + javascript = f""" + try {{ + var desc = new ActionDescriptor(); + desc.putUnitDouble(charIDToTypeID('Angl'), charIDToTypeID('#Ang'), {angle}); + executeAction(charIDToTypeID('Rtte'), desc, DialogModes.NO); + }} catch(e) {{ + // If rotation fails, cancel the transform and throw error + executeAction(charIDToTypeID('Trnf'), undefined, DialogModes.NO); + throw e; + }} + """ + + try: + self.adobe.doJavaScript(javascript) + except Exception as e: + raise PhotoshopPythonAPIError(f"Failed to rotate layer: {e!s}") diff --git a/photoshop/api/_artlayers.py b/photoshop/api/_artlayers.py index dddb7fa7..bd8d8241 100644 --- a/photoshop/api/_artlayers.py +++ b/photoshop/api/_artlayers.py @@ -1,72 +1,95 @@ # Import third-party modules +from __future__ import annotations + +from typing import Any, Union, overload + from comtypes import ArgumentError # Import local modules from photoshop.api._artlayer import ArtLayer -from photoshop.api._core import Photoshop +from photoshop.api._collection_base import CollectionBase from photoshop.api.errors import PhotoshopPythonAPIError -# pylint: disable=too-many-public-methods -class ArtLayers(Photoshop): - """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.""" +class ArtLayers(CollectionBase[ArtLayer]): + """The collection of art layer objects in the document. + + This class represents a collection of art layers in a Photoshop document. + It provides methods to: + - Add new layers + - Access layers by index or name + - Remove layers + - Iterate over layers + """ + + @overload + def __getitem__(self, key: int) -> ArtLayer: + """Get layer by index.""" + + @overload + def __getitem__(self, key: str) -> ArtLayer: + """Get layer by name.""" + + def __getitem__(self, key: Union[int, str]) -> ArtLayer: + """Access a given ArtLayer using dictionary key lookup. + + Args: + key: Either an integer index or a string name of the layer + + Returns: + ArtLayer: The requested art layer + + Raises: + PhotoshopPythonAPIError: If the layer could not be found + TypeError: If the key type is not int or str + """ 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]) + if isinstance(key, str): + return self.getByName(key) + if isinstance(key, int): + return self._wrap_item(self.app[key]) + raise TypeError(f"Key must be int or str, not {type(key)}") + except (ArgumentError, IndexError): + name_str = f'named "{key}"' if isinstance(key, str) else f"at index {key}" + raise PhotoshopPythonAPIError(f"Could not find an artLayer {name_str}") + + def add(self) -> ArtLayer: + """Add a new art layer to the document. + + Returns: + ArtLayer: The newly created art layer + """ + return self._wrap_item(self.app.add()) def getByName(self, name: str) -> ArtLayer: - """Get the first element in the collection with the provided name. - + """Get the first art layer with the specified name. + + Args: + name: The name of the layer to find + + Returns: + ArtLayer: The art layer with the specified name + Raises: - PhotoshopPythonAPIError: Could not find a artLayer. + PhotoshopPythonAPIError: If no layer with the specified name is found """ - for layer in self.app: + for layer in self: if layer.name == name: - return ArtLayer(layer) + return 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 removeAll(self) -> None: + """Delete all art layers in the collection.""" + for layer in self: + layer.remove() + + def _wrap_item(self, item: Any) -> ArtLayer: + """Wrap a COM art layer object in an ArtLayer instance. + + Args: + item: The COM art layer object to wrap + + Returns: + ArtLayer: The wrapped art layer + """ + return ArtLayer(item) diff --git a/photoshop/api/_channel.py b/photoshop/api/_channel.py index ee47309c..fee5e36e 100644 --- a/photoshop/api/_channel.py +++ b/photoshop/api/_channel.py @@ -1,66 +1,68 @@ -# Import local modules -from photoshop.api._core import Photoshop - - -# pylint: disable=too-many-public-methods -class Channel(Photoshop): - def __init__(self, parent): - super().__init__(parent=parent) - self._flag_as_method( - "duplicate", - "merge", - ) - - @property - def color(self): - return self.app.color - - @color.setter - def color(self, value): - self.app.color = value - - @property - def histogram(self): - return self.app.histogram - - @histogram.setter - def histogram(self, value): - self.app.histogram = value - - @property - def kind(self): - return self.app.kind - - @kind.setter - def kind(self, value): - self.app.kind = value - - @property - def opacity(self): - return self.app.opacity - - @opacity.setter - def opacity(self, value): - self.app.opacity = value - - @property - def visible(self): - return self.app.visible - - @visible.setter - def visible(self, value): - self.app.visible = value - - @property - def name(self): - return self.app.name - - def duplicate(self, targetDocument=None): - self.app.duplicate(targetDocument) - - def merge(self): - self.app.merge() - - def remove(self): - channel = f'app.activeDocument.channels.getByName("{self.name}")' - self.eval_javascript(f"{channel}.remove()") +# Import local modules +from __future__ import annotations + +from photoshop.api._core import Photoshop + + +# pylint: disable=too-many-public-methods +class Channel(Photoshop): + def __init__(self, parent): + super().__init__(parent=parent) + self._flag_as_method( + "duplicate", + "merge", + ) + + @property + def color(self): + return self.app.color + + @color.setter + def color(self, value): + self.app.color = value + + @property + def histogram(self): + return self.app.histogram + + @histogram.setter + def histogram(self, value): + self.app.histogram = value + + @property + def kind(self): + return self.app.kind + + @kind.setter + def kind(self, value): + self.app.kind = value + + @property + def opacity(self): + return self.app.opacity + + @opacity.setter + def opacity(self, value): + self.app.opacity = value + + @property + def visible(self): + return self.app.visible + + @visible.setter + def visible(self, value): + self.app.visible = value + + @property + def name(self): + return self.app.name + + def duplicate(self, targetDocument=None): + self.app.duplicate(targetDocument) + + def merge(self): + self.app.merge() + + def remove(self): + channel = f'app.activeDocument.channels.getByName("{self.name}")' + self.eval_javascript(f"{channel}.remove()") diff --git a/photoshop/api/_channels.py b/photoshop/api/_channels.py index 00b3b6a2..355f3e95 100644 --- a/photoshop/api/_channels.py +++ b/photoshop/api/_channels.py @@ -1,44 +1,60 @@ # 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) +from __future__ import annotations - def __len__(self): - return self.length +from typing import Any - 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) +from photoshop.api._channel import Channel +from photoshop.api._collection_base import CollectionBase +from photoshop.api.errors import PhotoshopPythonAPIError - def add(self): - self.app.add() - def removeAll(self): +class Channels(CollectionBase[Channel]): + """The collection of channel objects in the document. + + This class represents a collection of channels in a Photoshop document. + It provides methods to: + - Add new channels + - Access channels by index or name + - Remove channels + - Iterate over channels + """ + + def add(self) -> Channel: + """Add a new channel to the document. + + Returns: + Channel: The newly created channel + """ + return self._wrap_item(self.app.add()) + + def removeAll(self) -> None: + """Delete all channels in the collection.""" self.app.removeAll() - def getByName(self, name) -> Channel: - for channel in self._channels: + def getByName(self, name: str) -> Channel: + """Get the first channel with the specified name. + + Args: + name: The name of the channel to find + + Returns: + Channel: The channel with the specified name + + Raises: + PhotoshopPythonAPIError: If no channel with the specified name is found + """ + for channel in self: if channel.name == name: - return Channel(channel) + return channel raise PhotoshopPythonAPIError(f'Could not find a channel named "{name}"') + + def _wrap_item(self, item: Any) -> Channel: + """Wrap a COM channel object in a Channel instance. + + Args: + item: The COM channel object to wrap + + Returns: + Channel: The wrapped channel + """ + return Channel(item) diff --git a/photoshop/api/_collection_base.py b/photoshop/api/_collection_base.py new file mode 100644 index 00000000..fb5c6a82 --- /dev/null +++ b/photoshop/api/_collection_base.py @@ -0,0 +1,80 @@ +"""Base class for all collection-like objects in Photoshop API. + +This module provides a base class that implements common collection functionality +for Photoshop objects, making them more Pythonic by supporting operations like +len(), iteration, and indexing. +""" + +from __future__ import annotations +from typing import Any, Iterator, TypeVar, Generic + +from photoshop.api._core import Photoshop + +T = TypeVar("T") + +class CollectionBase(Photoshop, Generic[T]): + """Base class for all collection-like objects. + + This class provides common collection functionality and makes Photoshop + collection objects more Pythonic by implementing: + - len() support via __len__ + - iteration support via __iter__ + - indexing support via __getitem__ + + Args: + parent: The parent object that owns this collection + """ + + def __init__(self, parent: Any) -> None: + """Initialize the collection base. + + Args: + parent: The parent object that owns this collection + """ + super().__init__(parent=parent) + self._flag_as_method("add", "item") + + def __len__(self) -> int: + """Get the number of items in the collection. + + Returns: + int: The number of items in the collection + """ + return self.app.count + + def __iter__(self) -> Iterator[T]: + """Iterate over items in the collection. + + Yields: + The next item in the collection + """ + for item in self.app: + yield self._wrap_item(item) + + def __getitem__(self, key: int) -> T: + """Get an item by index. + + Args: + key: The index of the item to get + + Returns: + The item at the specified index + + Raises: + IndexError: If the index is out of range + """ + return self._wrap_item(self.app[key]) + + def _wrap_item(self, item: Any) -> T: + """Wrap a COM object in the appropriate Python class. + + This method should be overridden by subclasses to wrap COM objects + in the appropriate Python class. + + Args: + item: The COM object to wrap + + Returns: + The wrapped item + """ + raise NotImplementedError("Subclasses must implement _wrap_item") diff --git a/photoshop/api/_core.py b/photoshop/api/_core.py index 28be680a..ab673da4 100644 --- a/photoshop/api/_core.py +++ b/photoshop/api/_core.py @@ -1,17 +1,16 @@ """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 +from __future__ import annotations + 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 Any, List, Optional # Import third-party modules from comtypes.client import CreateObject @@ -31,12 +30,12 @@ class Photoshop: object_name: str = "Application" def __init__(self, ps_version: Optional[str] = None, parent: Any = None): - """ - Initialize the Photoshop core object. + """Initialize the Photoshop core object. Args: ps_version: Optional, Photoshop version to look for explicitly in registry. parent: Optional, parent instance to use as app object. + """ # Establish the initial app and program ID ps_version = os.getenv("PS_VERSION", ps_version) @@ -53,7 +52,7 @@ def __init__(self, ps_version: Optional[str] = None, parent: Any = None): if not self.app: # Attempt unsuccessful self._logger.debug( - f"Unable to retrieve Photoshop object '{self.typename}' using version '{ps_version}'." + f"Unable to retrieve Photoshop object '{self.typename}' using version '{ps_version}'.", ) # Look for version ID in registry data @@ -70,16 +69,16 @@ def __init__(self, ps_version: Optional[str] = None, parent: Any = None): self.app = parent self._has_parent = True - def __repr__(self): - return self + def __repr__(self) -> str: + return f"{self.__class__.__name__}()" - def __call__(self, *args, **kwargs): + def __call__(self, *args: Any, **kwargs: Any) -> Any: return self.app - def __str__(self): - return f"{self.__class__.__name__} <{self.program_name}>" + def __str__(self) -> str: + return f"{self.typename}" - def __getattribute__(self, item): + def __getattribute__(self, item: str) -> Any: try: return super().__getattribute__(item) except AttributeError: @@ -92,12 +91,12 @@ 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 os.getenv("PS_DEBUG", "").lower() in ("true", "1", "t") @cached_property def _logger(self) -> Logger: """Logger: Logging object for warning output.""" - logr = getLogger("photoshop") + logr = getLogger(self.typename) logr.setLevel(DEBUG if self._debug else CRITICAL) return logr @@ -112,25 +111,7 @@ def typename(self) -> str: @property def program_name(self) -> str: - """str: Formatted program name found in the Windows Classes registry, e.g. Photoshop.Application.140. - - Examples: - - Photoshop.ActionDescriptor - - Photoshop.ActionDescriptor.140 - - Photoshop.ActionList - - Photoshop.ActionList.140 - - Photoshop.ActionReference - - Photoshop.ActionReference.140 - - Photoshop.Application - - Photoshop.Application.140 - - Photoshop.BatchOptions - - Photoshop.BatchOptions.140 - - Photoshop.BitmapConversionOptions - - Photoshop.BMPSaveOptions - - Photoshop.BMPSaveOptions.140 - - Photoshop.CameraRAWOpenOptions - - Photoshop.CameraRAWOpenOptions.140 - """ + """str: Formatted program name found in the Windows Classes registry, e.g. Photoshop.Application.140.""" if self.app_id: return f"{self._root}.{self.object_name}.{self.app_id}" return f"{self._root}.{self.object_name}" @@ -141,16 +122,15 @@ 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): - """ - * This is a hack for Photoshop's broken COM implementation. + 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 getting a field from the COM object, comtypes will first try to fetch it as a property, then treat it as a method if it fails. @@ -172,9 +152,8 @@ def _get_photoshop_versions(self) -> List[str]: 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]: - """ - Try each version string until a valid Photoshop application Dispatch object is returned. + def _get_application_object(self, versions: Optional[List[str]] = None) -> Optional[Dispatch]: + """Try each version string until a valid Photoshop application Dispatch object is returned. Args: versions: List of Photoshop version ID's found in registry. @@ -184,12 +163,15 @@ def _get_application_object(self, versions: List[str] = None) -> Optional[Dispat Raises: OSError: If a Dispatch object wasn't resolved. + """ + if versions is None: + versions = [] for v in versions: self.app_id = v with suppress(OSError): return CreateObject(self.program_name, dynamic=True) - return + return None """ * Public Methods @@ -197,24 +179,30 @@ def _get_application_object(self, versions: List[str] = None) -> Optional[Dispat def get_application_path(self) -> str: """str: The absolute path of Photoshop installed location.""" - key = self.open_key(f"{self._reg_path}\\{self.program_id}") + print(f"{self._reg_path}\\{self.app_id}") + key = self._open_key(f"{self._reg_path}\\{self.app_id}") return winreg.QueryValueEx(key, "ApplicationPath")[0] def get_plugin_path(self) -> str: """str: The absolute plugin path of Photoshop.""" - return os.path.join(self.application_path, "Plug-ins") + key = self._open_key(f"{self._reg_path}\\{self.app_id}\\PluginPath") + return winreg.QueryValueEx(key, "")[0] def get_presets_path(self) -> str: """str: The absolute presets path of Photoshop.""" - return os.path.join(self.application_path, "Presets") + key = self._open_key(f"{self._reg_path}\\{self.app_id}\\PresetsPath") + return winreg.QueryValueEx(key, "")[0] def get_script_path(self) -> str: """str: The absolute scripts path of Photoshop.""" - return os.path.join(self.presets_path, "Scripts") + key = self._open_key(f"{self._reg_path}\\{self.app_id}\\ScriptPath") + return winreg.QueryValueEx(key, "")[0] - 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) -> Any: """Instruct the application to execute javascript code.""" - executor = self.adobe if self._has_parent else self.app + executor = self.app + if self._has_parent: + executor = self.adobe return executor.doJavaScript(javascript, Arguments, ExecutionMode) """ @@ -222,7 +210,7 @@ def eval_javascript(self, javascript: str, Arguments: Any = None, ExecutionMode: """ @staticmethod - def _open_key(key: str) -> winreg.HKEYType: + def _open_key(key: str) -> Any: """Open the register key. Args: @@ -233,6 +221,7 @@ def _open_key(key: str) -> winreg.HKEYType: Raises: OSError: if registry key cannot be read. + """ machine_type = platform.machine() mappings = {"AMD64": winreg.KEY_WOW64_64KEY} @@ -242,5 +231,5 @@ def _open_key(key: str) -> winreg.HKEYType: 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..fac642e7 100644 --- a/photoshop/api/_document.py +++ b/photoshop/api/_document.py @@ -9,17 +9,13 @@ documents in the list by index, or use Documents.getByName() to retrieve them by name. - Create documents programmatically using the Documents.add() method. - - """ # Import built-in modules +from __future__ import annotations + 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 Any, List, Optional, TypeVar, Union # Import third-party modules from comtypes import COMError @@ -29,34 +25,23 @@ from photoshop.api._artlayers import ArtLayers from photoshop.api._channels import Channels from photoshop.api._core import Photoshop -from photoshop.api._documentinfo import DocumentInfo -from photoshop.api._layerComps import LayerComps 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.save_options import ExportOptionsSaveForWeb +from photoshop.api.enumerations import ExportType, ExtensionType, SaveOptions, TrimType +PS_Layer = TypeVar("PS_Layer", ArtLayer, LayerSet) -# Custom types. -PS_Layer = TypeVar("PS_Layer", LayerSet, ArtLayer) - -# pylint: disable=too-many-public-methods class Document(Photoshop): """The active containment object for the layers and all other objects in the script. the basic canvas for the file. - - """ # noqa: E501 + """ object_name = "Application" - def __init__(self, parent): + def __init__(self, parent: Any) -> None: super().__init__(parent=parent) self._flag_as_method( "autoCount", @@ -82,8 +67,19 @@ def __init__(self, parent): @property def artLayers(self) -> ArtLayers: + """The art layers collection in the document.""" return ArtLayers(self.app.artLayers) + @property + def layers(self) -> Layers: + """The layers collection in the document.""" + return Layers(self.app.layers) + + @property + def layerSets(self) -> LayerSets: + """The layer sets collection in the document.""" + return LayerSets(self.app.layerSets) + @property def activeLayer(self) -> PS_Layer: """The selected layer.""" @@ -93,265 +89,128 @@ def activeLayer(self) -> PS_Layer: return func(self.app.activeLayer) @activeLayer.setter - def activeLayer(self, layer) -> NoReturn: + def activeLayer(self, layer: PS_Layer) -> None: """Sets the select layer as active layer. Args: - layer (._artlayer.ArtLayer or - ._layerSet.LayerSet): The artLayer. + layer: The artLayer or layerSet to set as active. """ - self.app.activeLayer = layer + self.app.activeLayer = layer.app @property - def activeChannels(self): + def activeChannels(self) -> List[Any]: """The selected channels.""" - return self.app.activeChannels + return list(self.app.activeChannels) @activeChannels.setter - def activeChannels(self, channels): + def activeChannels(self, channels: List[Any]) -> None: self.app.activeChannels = channels @property - def activeHistoryBrushSource(self): + def activeHistoryBrushSource(self) -> Any: """The history state to use with the history brush.""" return self.app.activeHistoryBrushSource @property - def activeHistoryState(self): + def activeHistoryState(self) -> Any: """The current history state for this document.""" return self.app.activeHistoryState @activeHistoryState.setter - def activeHistoryState(self, state): + def activeHistoryState(self, state: Any) -> 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) -> int: """The number of bits per channel.""" return self.app.bitsPerChannel @bitsPerChannel.setter - def bitsPerChannel(self, value): + def bitsPerChannel(self, value: int) -> None: self.app.bitsPerChannel = value @property - def channels(self): + def channels(self) -> Channels: return Channels(self.app.channels) @property - def colorProfileName(self): - """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 + def colorProfileName(self) -> str: + """The name of the color profile.""" + return str(self.app.colorProfileName) @colorProfileName.setter - def colorProfileName(self, name): + def colorProfileName(self, name: str) -> None: self.app.colorProfileName = name @property - def colorProfileType(self): - """The type of color model that defines the working space of the - Document.""" + def colorProfileType(self) -> Any: + """The type of color model that defines the working space.""" return self.app.colorProfileType @colorProfileType.setter - def colorProfileType(self, profile_type): + def colorProfileType(self, profile_type: Any) -> None: self.app.colorProfileType = profile_type @property - def colorSamplers(self): + def colorSamplers(self) -> List[Any]: """The current color samplers associated with the Document.""" - return self.app.colorSamplers + return list(self.app.colorSamplers) @property - def componentChannels(self): + def componentChannels(self) -> List[Any]: """The color component channels for this Document.""" - return self.app.componentChannels + return list(self.app.componentChannels) @property - def countItems(self): + def countItems(self) -> List[Any]: """The current count items in the Document.""" - return self.app.countItems + return list(self.app.countItems) @property - def fullName(self): + def fullName(self) -> str: """The full path name of the Document.""" try: - return Path(self.app.fullName) + return str(self.app.fullName) except COMError: - self.eval_javascript( - 'alert ("Please save your Document first!",' '"{}")'.format(self.name), - ) + return "" @property - def height(self): + def height(self) -> float: """The height of the Document.""" - return self.app.Height - - @property - def histogram(self): - """A histogram showing the number of pixels at each color intensity - level for the composite channel.""" - return self.app.Histogram - - @property - def history_states(self): - """The history states collection in this Document.""" - return self.app.HistoryStates - - @property - def id(self): - """The unique ID of this Document.""" - return self.app.Id - - @property - def info(self): - """Metadata about the Document.""" - return DocumentInfo(self.app.info) - - @property - def layerComps(self): - """The layer comps collection in this Document.""" - return LayerComps(self.app.layerComps) - - @property - def layers(self): - """The layers collection in the Document.""" - return Layers(self.app.Layers) - - @property - def layerSets(self): - """The layer sets collection in the Document.""" - return LayerSets(self.app.layerSets) - - @property - def managed(self): - """If true, the Document is a workgroup Document.""" - return self.app.Managed - - @property - def measurement_scale(self): - """The measurement scale of the Document.""" - return self.app.MeasurementScale - - @property - def mode(self): - """The color profile.""" - return self.app.Mode - - @property - def name(self) -> str: - """The Document name.""" - return self.app.name - - @property - def parent(self): - """The object's container.""" - return self.app.Parent - - @property - def path(self) -> str: - """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), - ) - - @path.setter - def path(self, path: str) -> NoReturn: - self.app.fullName = path - - @property - def pathItems(self): - return self.app.pathItems + return float(self.app.height) @property - def pixelAspectRatio(self): - """The (custom) pixel aspect ratio of the Document. + def histogram(self) -> List[int]: + """A histogram showing the number of pixels at each color intensity level.""" + return list(self.app.histogram) - Range: 0.100 to 10.000. - - """ - return self.app.pixelAspectRatio - - @property - def printSettings(self): - """Document print settings.""" - return self.app.printSettings - - @property - def quickMaskMode(self): - """If true, the document is in Quick Mask mode.""" - return self.app.quickMaskMode - - @property - def saved(self): - """If true, the Document been saved since the last change.""" - return self.app.Saved - - @property - def resolution(self): - """The resolution of the Document (in pixels per inch)""" - return self.app.resolution - - @property - def selection(self): - """The selected area of the Document.""" - return Selection(self.app.selection) - - @property - def typename(self): - """The class name of the object.""" - return self.app.typename - - @property - def cloudDocument(self): - """This document is in the cloud.""" - return self.app.cloudDocument - - @property - def cloudWorkAreaDirectory(self): - """Local directory for this cloud document.""" - return self.app.cloudWorkAreaDirectory - - @property - def width(self): - return self.app.Width - - @property - def xmpMetadata(self): - """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, *args: Any, **kwargs: Any) -> Any: """Counts the objects in the Document.""" return self.app.autoCount(*args, **kwargs) - def changeMode(self, *args, **kwargs): + def changeMode(self, *args: Any, **kwargs: Any) -> Any: """Changes the mode of the Document.""" return self.app.changeMode(*args, **kwargs) - def close(self, saving=SaveOptions.DoNotSaveChanges): + def close(self, saving: SaveOptions = SaveOptions.DoNotSaveChanges) -> None: + """Closes the document.""" return self.app.close(saving) - def convertProfile(self): + def convertProfile(self) -> None: + """Converts the color profile of the document.""" return self.app.convertProfile() - def flatten(self): + def flatten(self) -> None: """Flattens all layers.""" - return self.app.Flatten() + return self.app.flatten() - def mergeVisibleLayers(self): + def mergeVisibleLayers(self) -> None: """Flattens all visible layers in the Document.""" return self.app.mergeVisibleLayers() @@ -361,7 +220,7 @@ def crop( angle: Optional[float] = None, width: Optional[int] = None, height: Optional[int] = None, - ): + ) -> None: """Crops the document. Args: @@ -373,98 +232,126 @@ def crop( """ return self.app.crop(bounds, angle, width, height) - def exportDocument(self, file_path: str, exportAs: ExportType, options: Union[ExportOptionsSaveForWeb]): + def exportDocument( + self, + file_path: Union[str, Path], + exportAs: ExportType, + options: Any, + ) -> None: """Exports the Document. - Note: - This is a patched version, Due to the problem of dynamic binding, - we cannot call it directly, so this command is executed by javascript. - - References: - - https://stackoverflow.com/questions/12286761/saving-a-png-with-photoshop-script-not-working + Args: + file_path: Path where to save the exported file. + exportAs: The export type. + options: The export options. """ - file_path = file_path.replace("\\", "/") - self.app.export(file_path, exportAs, options) + if isinstance(file_path, Path): + file_path = str(file_path) + return self.eval_javascript( + f'app.activeDocument.exportDocument("{file_path}", {exportAs}, {options})', + ) + + def duplicate( + self, + name: Optional[str] = None, + merge_layers_only: bool = False, + ) -> Document: + """Duplicates this document. + + Args: + name: The name of the new document. + merge_layers_only: If true, duplicate merged layers only. + + Returns: + The duplicated Document object. - def duplicate(self, name=None, merge_layers_only=False): + """ return Document(self.app.duplicate(name, merge_layers_only)) - def paste(self): + def paste(self) -> None: """Pastes contents of the clipboard into the Document.""" - self.eval_javascript("app.activeDocument.paste()") - return self.activeLayer + return self.app.paste() - def print(self): + def print(self) -> None: """Prints the document.""" return self.app.print() - def printOneCopy(self): - self.app.printOneCopy() + def printOneCopy(self) -> None: + """Print one copy of the document.""" + return self.app.printOneCopy() - def rasterizeAllLayers(self): + def rasterizeAllLayers(self) -> None: + """Rasterize all layers.""" return self.app.rasterizeAllLayers() - def recordMeasurements(self, source, dataPoints): + def recordMeasurements(self, source: Any, dataPoints: List[Any]) -> None: """Records the measurements of document.""" - self.app.recordMeasurements(source, dataPoints) + return 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() - def save(self): + def save(self) -> None: """Saves the Document.""" return self.app.save() - def saveAs(self, file_path, options, asCopy=True, extensionType=ExtensionType.Lowercase): - """Saves the documents with the specified save options. + def saveAs( + self, + file_path: Union[str, Path], + options: Any, + asCopy: bool = True, + extensionType: ExtensionType = ExtensionType.Lowercase, + ) -> None: + """Saves the document 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 file. + options: Save options. + asCopy: If true, saves as a copy. + extensionType: The case of the extension. + """ + if isinstance(file_path, Path): + file_path = str(file_path) return self.app.saveAs(file_path, options, asCopy, extensionType) - def splitChannels(self): + def splitChannels(self) -> List[Document]: """Splits the channels of the document.""" - self.app.splitChannels() + return [Document(channel) for channel in 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. + Args: + historyString: The history state name. + javaScriptString: The JavaScript code to execute. """ - self.eval_javascript(f"app.activeDocument.suspendHistory('{historyString}', '{javaScriptString}')") + return self.app.suspendHistory(historyString, javaScriptString) - def trap(self, width: int): - """ - Applies trapping to a CMYK document. - Valid only when ‘mode’ = CMYK. + def trap(self, width: int) -> None: + """Applies trapping to a CMYK document. + + Args: + width: The trap width in pixels. """ - self.app.trap(width) + return self.app.trap(width) def trim( self, trim_type: TrimType, - top: Optional[bool] = True, - left: Optional[bool] = True, - bottom: Optional[bool] = True, - right: Optional[bool] = True, - ): - """Trims the transparent area around the image on the specified sides of the canvas. + top: bool = True, + left: bool = True, + bottom: bool = True, + right: bool = True, + ) -> None: + """Trims the transparent area around the image. Args: trim_type: The color or type of pixels to base the trim on. - - Examples: - - TrimType.BottomRightPixel - - TrimType.TopLeftPixel - - TrimType.TransparentPixels - top: If true, trims away the top of the document. left: If true, trims away the left of the document. bottom: If true, trims away the bottom of the document. @@ -473,7 +360,13 @@ def trim( """ return self.app.trim(trim_type, top, left, bottom, right) - def resizeImage(self, width: int, height: int, resolution: int = 72, automatic: int = 8): + def resizeImage( + self, + width: int, + height: int, + resolution: int = 72, + automatic: int = 8, + ) -> None: """Changes the size of the image. Args: diff --git a/photoshop/api/_documentinfo.py b/photoshop/api/_documentinfo.py index c24068cd..1c2137a0 100644 --- a/photoshop/api/_documentinfo.py +++ b/photoshop/api/_documentinfo.py @@ -1,243 +1,245 @@ -"""Metadata about a document object. - -These values can be set by selecting File > File Info in the Adobe Photoshop -application. - -""" - -# Import built-in modules -from pprint import pformat - -# Import local modules -from photoshop.api._core import Photoshop - - -# pylint: disable=too-many-public-methods -class DocumentInfo(Photoshop): - """Metadata about a document object.""" - - def __init__(self, parent): - super().__init__(parent=parent) - - def __str__(self): - return pformat( - { - "author": self.author, - "authorPosition": self.authorPosition, - "caption": self.caption, - "captionWriter": self.captionWriter, - "category": self.category, - "city": self.city, - "country": self.country, - "copyrightNotice": self.copyrightNotice, - "copyrighted": self.copyrighted, - "creationDate": self.creationDate, - "credit": self.credit, - "exif": self.exif, - "headline": self.headline, - "instructions": self.instructions, - "jobName": self.jobName, - "keywords": self.keywords, - "provinceState": self.provinceState, - "source": self.source, - "ownerUrl": self.ownerUrl, - "supplementalCategories": self.supplementalCategories, - "title": self.title, - "transmissionReference": self.transmissionReference, - "urgency": self.urgency, - } - ) - - @property - def author(self): - """str: The author.""" - return self.app.author - - @author.setter - def author(self, name): - self.app.author = name - - @property - def authorPosition(self): - """str:The author’s position.""" - return self.app.authorPosition - - @authorPosition.setter - def authorPosition(self, name): - self.app.authorPosition = name - - @property - def caption(self): - return self.app.caption - - @caption.setter - def caption(self, name): - self.app.caption = name - - @property - def captionWriter(self): - return self.app.captionWriter - - @captionWriter.setter - def captionWriter(self, name): - self.app.captionWriter = name - - @property - def category(self): - """str: The document category.""" - return self.app.category - - @category.setter - def category(self, name): - self.app.category = name - - @property - def city(self): - return self.app.city - - @city.setter - def city(self, city_name): - self.app.city = city_name - - @property - def copyrightNotice(self): - """str: The copyright notice.""" - return self.app.copyrightNotice - - @copyrightNotice.setter - def copyrightNotice(self, name): - self.app.copyrightNotice = name - - @property - def copyrighted(self): - """str: The copyright status.""" - return self.app.copyrighted - - @copyrighted.setter - def copyrighted(self, info): - self.app.copyrighted = info - - @property - def country(self): - return self.app.country - - @country.setter - def country(self, name): - self.app.country = name - - @property - def creationDate(self): - return self.app.creationDate - - @creationDate.setter - def creationDate(self, name): - self.app.creationDate = name - - @property - def credit(self): - """str: The author credit.""" - return self.app.credit - - @credit.setter - def credit(self, value): - self.app.credit = value - - @property - def exif(self): - return self.app.exif - - @exif.setter - def exif(self, info): - self.app.exif = info - - @property - def headline(self): - return self.app.headline - - @headline.setter - def headline(self, value): - self.app.headline = value - - @property - def instructions(self): - return self.app.instructions - - @instructions.setter - def instructions(self, value): - self.app.instructions = value - - @property - def jobName(self): - return self.app.jobName - - @jobName.setter - def jobName(self, job): - self.app.jobName = job - - @property - def keywords(self): - return self.app.keywords - - @keywords.setter - def keywords(self, words): - self.app.keywords = words - - @property - def ownerUrl(self): - return self.app.ownerUrl - - @ownerUrl.setter - def ownerUrl(self, url): - self.app.ownerUrl = url - - @property - def provinceState(self): - """str: The state or province.""" - return self.app.provinceState - - @provinceState.setter - def provinceState(self, state_name): - self.app.provinceState = state_name - - @property - def source(self): - return self.app.source - - @source.setter - def source(self, source_name): - self.app.source = source_name - - @property - def supplementalCategories(self): - """str: Other categories.""" - return self.app.supplementalCategories - - @supplementalCategories.setter - def supplementalCategories(self, info): - self.app.supplementalCategories = info - - @property - def title(self): - return self.app.title - - @title.setter - def title(self, name): - self.app.title = name - - @property - def transmissionReference(self): - """str: The transmission reference.""" - return self.app.transmissionReference - - @transmissionReference.setter - def transmissionReference(self, reference): - self.app.transmissionReference = reference - - @property - def urgency(self): - """The document urgency.""" - return self.app.urgency - - @urgency.setter - def urgency(self, status): - self.app.urgency = status +"""Metadata about a document object. + +These values can be set by selecting File > File Info in the Adobe Photoshop +application. + +""" + +# Import built-in modules +from __future__ import annotations + +from pprint import pformat + +# Import local modules +from photoshop.api._core import Photoshop + + +# pylint: disable=too-many-public-methods +class DocumentInfo(Photoshop): + """Metadata about a document object.""" + + def __init__(self, parent): + super().__init__(parent=parent) + + def __str__(self): + return pformat( + { + "author": self.author, + "authorPosition": self.authorPosition, + "caption": self.caption, + "captionWriter": self.captionWriter, + "category": self.category, + "city": self.city, + "country": self.country, + "copyrightNotice": self.copyrightNotice, + "copyrighted": self.copyrighted, + "creationDate": self.creationDate, + "credit": self.credit, + "exif": self.exif, + "headline": self.headline, + "instructions": self.instructions, + "jobName": self.jobName, + "keywords": self.keywords, + "provinceState": self.provinceState, + "source": self.source, + "ownerUrl": self.ownerUrl, + "supplementalCategories": self.supplementalCategories, + "title": self.title, + "transmissionReference": self.transmissionReference, + "urgency": self.urgency, + }, + ) + + @property + def author(self): + """str: The author.""" + return self.app.author + + @author.setter + def author(self, name): + self.app.author = name + + @property + def authorPosition(self): + """str:The author’s position.""" + return self.app.authorPosition + + @authorPosition.setter + def authorPosition(self, name): + self.app.authorPosition = name + + @property + def caption(self): + return self.app.caption + + @caption.setter + def caption(self, name): + self.app.caption = name + + @property + def captionWriter(self): + return self.app.captionWriter + + @captionWriter.setter + def captionWriter(self, name): + self.app.captionWriter = name + + @property + def category(self): + """str: The document category.""" + return self.app.category + + @category.setter + def category(self, name): + self.app.category = name + + @property + def city(self): + return self.app.city + + @city.setter + def city(self, city_name): + self.app.city = city_name + + @property + def copyrightNotice(self): + """str: The copyright notice.""" + return self.app.copyrightNotice + + @copyrightNotice.setter + def copyrightNotice(self, name): + self.app.copyrightNotice = name + + @property + def copyrighted(self): + """str: The copyright status.""" + return self.app.copyrighted + + @copyrighted.setter + def copyrighted(self, info): + self.app.copyrighted = info + + @property + def country(self): + return self.app.country + + @country.setter + def country(self, name): + self.app.country = name + + @property + def creationDate(self): + return self.app.creationDate + + @creationDate.setter + def creationDate(self, name): + self.app.creationDate = name + + @property + def credit(self): + """str: The author credit.""" + return self.app.credit + + @credit.setter + def credit(self, value): + self.app.credit = value + + @property + def exif(self): + return self.app.exif + + @exif.setter + def exif(self, info): + self.app.exif = info + + @property + def headline(self): + return self.app.headline + + @headline.setter + def headline(self, value): + self.app.headline = value + + @property + def instructions(self): + return self.app.instructions + + @instructions.setter + def instructions(self, value): + self.app.instructions = value + + @property + def jobName(self): + return self.app.jobName + + @jobName.setter + def jobName(self, job): + self.app.jobName = job + + @property + def keywords(self): + return self.app.keywords + + @keywords.setter + def keywords(self, words): + self.app.keywords = words + + @property + def ownerUrl(self): + return self.app.ownerUrl + + @ownerUrl.setter + def ownerUrl(self, url): + self.app.ownerUrl = url + + @property + def provinceState(self): + """str: The state or province.""" + return self.app.provinceState + + @provinceState.setter + def provinceState(self, state_name): + self.app.provinceState = state_name + + @property + def source(self): + return self.app.source + + @source.setter + def source(self, source_name): + self.app.source = source_name + + @property + def supplementalCategories(self): + """str: Other categories.""" + return self.app.supplementalCategories + + @supplementalCategories.setter + def supplementalCategories(self, info): + self.app.supplementalCategories = info + + @property + def title(self): + return self.app.title + + @title.setter + def title(self, name): + self.app.title = name + + @property + def transmissionReference(self): + """str: The transmission reference.""" + return self.app.transmissionReference + + @transmissionReference.setter + def transmissionReference(self, reference): + self.app.transmissionReference = reference + + @property + def urgency(self): + """The document urgency.""" + return self.app.urgency + + @urgency.setter + def urgency(self, status): + self.app.urgency = status diff --git a/photoshop/api/_documents.py b/photoshop/api/_documents.py index 365aaefc..d5eb7227 100644 --- a/photoshop/api/_documents.py +++ b/photoshop/api/_documents.py @@ -1,23 +1,16 @@ # Import local modules -from photoshop.api._core import Photoshop +from __future__ import annotations + +from photoshop.api._collection_base import CollectionBase 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.enumerations import BitsPerChannelType, DocumentFill, NewDocumentMode from photoshop.api.errors import PhotoshopPythonAPIError # pylint: disable=too-many-public-methods, too-many-arguments -class Documents(Photoshop): +class Documents(CollectionBase[Document]): """The collection of open documents.""" - def __init__(self, parent): - super().__init__(parent=parent) - self._flag_as_method("add") - - def __len__(self) -> int: - return self.length - def add( self, width: int = 960, @@ -33,22 +26,21 @@ 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) - name (str): The name of the document. - mode (): The document mode. - initialFill : The initial fill of the document. + width: The width of the document (in pixels). + height: The height of the document (in pixels). + resolution: The resolution of the document (in pixels per inch) + name: The name of the document. + mode: The document mode. + initialFill: The initial fill of the document. pixelAspectRatio: The initial pixel aspect ratio of the document. - Default is `1.0`, the range is `0.1-10.00`. + Default is `1.0`, the range is `0.1-10.00`. bitsPerChannel: The number of bits per channel. colorProfileName: The name of color profile for document. Returns: - .Document: Document instance. - + Document: The newly created document instance. """ - return Document( + return self._wrap_item( self.app.add( width, height, @@ -59,27 +51,33 @@ def add( pixelAspectRatio, bitsPerChannel, 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: + """Get document by given document name. + + Args: + document_name: The name of the document to find + + Returns: + Document: The document with the specified name + + Raises: + PhotoshopPythonAPIError: If no document with the specified name is found + """ + for doc in self: if doc.name == document_name: - return Document(doc) + return doc raise PhotoshopPythonAPIError(f'Could not find a document named "{document_name}"') + + def _wrap_item(self, item: Any) -> Document: + """Wrap a COM document object in a Document instance. + + Args: + item: The COM document object to wrap + + Returns: + Document: The wrapped document + """ + return Document(item) diff --git a/photoshop/api/_layerComp.py b/photoshop/api/_layerComp.py index 79e60049..027c4257 100644 --- a/photoshop/api/_layerComp.py +++ b/photoshop/api/_layerComp.py @@ -1,100 +1,102 @@ -# Import local modules -from photoshop.api._core import Photoshop - - -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): - super().__init__(parent=parent) - self._flag_as_method( - "apply", - "recapture", - "remove", - "resetfromComp", - ) - - def __len__(self): - return self.length - - @property - def appearance(self): - return self.app.appearance - - @appearance.setter - def appearance(self, value): - self.app.appearance = value - - @property - def childLayerCompState(self): - return self.app.childLayerCompState - - @childLayerCompState.setter - def childLayerCompState(self, value): - self.app.childLayerCompState = value - - @property - def comment(self): - return self.app.comment - - @comment.setter - def comment(self, text): - self.app.comment = text - - @property - def name(self): - return self.app.name - - @name.setter - def name(self, text): - self.app.name = text - - @property - def parent(self): - return self.app.parent - - @property - def position(self): - return self.app.position - - @position.setter - def position(self, value): - self.app.position = value - - @property - def selected(self): - """True if the layer comp is currently selected.""" - return self.app.selected - - @selected.setter - def selected(self, value): - self.app.selected = value - - @property - def typename(self): - return self.app.typename - - @property - def visibility(self): - """True to use layer visibility settings.""" - return self.app.visibility - - @visibility.setter - def visibility(self, value): - self.app.visibility = value - - def apply(self): - """Applies the layer comp to the document.""" - self.app.apply() - - def recapture(self): - """Recaptures the current layer state(s) for this layer comp.""" - self.app.recapture() - - def remove(self): - """Deletes the layerComp object.""" - self.app.remove() - - def resetfromComp(self): - """Resets the layer comp state to thedocument state.""" - self.app.resetfromComp() +# Import local modules +from __future__ import annotations + +from photoshop.api._core import Photoshop + + +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): + super().__init__(parent=parent) + self._flag_as_method( + "apply", + "recapture", + "remove", + "resetfromComp", + ) + + def __len__(self): + return self.length + + @property + def appearance(self): + return self.app.appearance + + @appearance.setter + def appearance(self, value): + self.app.appearance = value + + @property + def childLayerCompState(self): + return self.app.childLayerCompState + + @childLayerCompState.setter + def childLayerCompState(self, value): + self.app.childLayerCompState = value + + @property + def comment(self): + return self.app.comment + + @comment.setter + def comment(self, text): + self.app.comment = text + + @property + def name(self): + return self.app.name + + @name.setter + def name(self, text): + self.app.name = text + + @property + def parent(self): + return self.app.parent + + @property + def position(self): + return self.app.position + + @position.setter + def position(self, value): + self.app.position = value + + @property + def selected(self): + """True if the layer comp is currently selected.""" + return self.app.selected + + @selected.setter + def selected(self, value): + self.app.selected = value + + @property + def typename(self): + return self.app.typename + + @property + def visibility(self): + """True to use layer visibility settings.""" + return self.app.visibility + + @visibility.setter + def visibility(self, value): + self.app.visibility = value + + def apply(self): + """Applies the layer comp to the document.""" + self.app.apply() + + def recapture(self): + """Recaptures the current layer state(s) for this layer comp.""" + self.app.recapture() + + def remove(self): + """Deletes the layerComp object.""" + self.app.remove() + + def resetfromComp(self): + """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..3603cb09 100644 --- a/photoshop/api/_layerComps.py +++ b/photoshop/api/_layerComps.py @@ -1,58 +1,88 @@ # Import local modules -from photoshop.api._core import Photoshop +from __future__ import annotations + +from typing import Any + +from photoshop.api._collection_base import CollectionBase from photoshop.api._layerComp import LayerComp from photoshop.api.errors import PhotoshopPythonAPIError -class LayerComps(Photoshop): - """The layer comps collection in this document.""" +class LayerComps(CollectionBase[LayerComp]): + """The layer comps collection in this document. + + This class represents a collection of layer compositions in a Photoshop document. + Layer comps are snapshots of the state of the Layers palette. Layer comps save + layer visibility and position states. + + It provides methods to: + - Add new layer comps + - Access layer comps by index or name + - Remove layer comps + - Iterate over layer comps + """ - def __init__(self, parent): - super().__init__(parent=parent) - self._flag_as_method( - "add", - "removeAll", + def add( + self, + name: str, + comment: str = "No Comment.", + appearance: bool = True, + position: bool = True, + visibility: bool = True, + childLayerCompStat: bool = False, + ) -> LayerComp: + """Add a new layer comp to the document. + + Args: + name: The name of the layer comp + comment: The description of the layer comp + appearance: If true, save the layer appearance + position: If true, save the layer position + visibility: If true, save the layer visibility + childLayerCompStat: If true, save the child layer comp state + + Returns: + LayerComp: The newly created layer comp + """ + return self._wrap_item( + self.app.add( + name, + comment, + appearance, + position, + visibility, + childLayerCompStat, + ), ) - def __len__(self): - return self.length - - @property - def length(self): - return len(self._layers) + def getByName(self, name: str) -> LayerComp: + """Get the first layer comp with the specified name. + + Args: + name: The name of the layer comp to find + + Returns: + LayerComp: The layer comp with the specified name + + Raises: + PhotoshopPythonAPIError: If no layer comp with the specified name is found + """ + for layer_comp in self: + if layer_comp.name == name: + return layer_comp + raise PhotoshopPythonAPIError(f'Could not find a layer comp named "{name}"') - @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 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): + def removeAll(self) -> None: + """Delete all layer comps in the collection.""" self.app.removeAll() - def __iter__(self): - for layer in self._layers: - yield LayerComp(layer) + def _wrap_item(self, item: Any) -> LayerComp: + """Wrap a COM layer comp object in a LayerComp instance. + + Args: + item: The COM layer comp object to wrap + + Returns: + LayerComp: The wrapped layer comp + """ + return LayerComp(item) diff --git a/photoshop/api/_layerSet.py b/photoshop/api/_layerSet.py index 3351da61..b53431f3 100644 --- a/photoshop/api/_layerSet.py +++ b/photoshop/api/_layerSet.py @@ -1,144 +1,145 @@ -# Import local modules -from photoshop.api._artlayer import ArtLayer -from photoshop.api._artlayers import ArtLayers -from photoshop.api._core import Photoshop -from photoshop.api._layers import Layers -from photoshop.api.enumerations import AnchorPosition -from photoshop.api.enumerations import BlendMode - - -class LayerSet(Photoshop): - """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): - super().__init__(parent=parent) - self._flag_as_method( - "merge", - "duplicate", - "add", - "delete", - "link", - "move", - "resize", - "rotate", - "translate", - "unlink", - ) - - @property - def allLocked(self): - return self.app.allLocked - - @allLocked.setter - def allLocked(self, value): - self.app.allLocked = value - - @property - def artLayers(self): - 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 - - @enabledChannels.setter - def enabledChannels(self, value): - self.app.enabledChannels = value - - @property - def layers(self): - return Layers(self.app.layers) - - @property - def layerSets(self): - # 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): - return LayerSet(self.app.duplicate(relativeObject, insertionLocation)) - - def link(self, with_layer): - self.app.link(with_layer) - - def add(self): - """Adds an element.""" - 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): - for layer in self.app: - yield layer +# Import local modules +from __future__ import annotations + +from photoshop.api._artlayer import ArtLayer +from photoshop.api._artlayers import ArtLayers +from photoshop.api._core import Photoshop +from photoshop.api._layers import Layers +from photoshop.api.enumerations import AnchorPosition, BlendMode + + +class LayerSet(Photoshop): + """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): + super().__init__(parent=parent) + self._flag_as_method( + "merge", + "duplicate", + "add", + "delete", + "link", + "move", + "resize", + "rotate", + "translate", + "unlink", + ) + + @property + def allLocked(self): + return self.app.allLocked + + @allLocked.setter + def allLocked(self, value): + self.app.allLocked = value + + @property + def artLayers(self): + 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 + + @enabledChannels.setter + def enabledChannels(self, value): + self.app.enabledChannels = value + + @property + def layers(self): + return Layers(self.app.layers) + + @property + def layerSets(self): + # 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): + return LayerSet(self.app.duplicate(relativeObject, insertionLocation)) + + def link(self, with_layer): + self.app.link(with_layer) + + def add(self): + """Adds an element.""" + 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): + for layer in self.app: + yield layer diff --git a/photoshop/api/_layerSets.py b/photoshop/api/_layerSets.py index 8b5b3aef..b183d66c 100644 --- a/photoshop/api/_layerSets.py +++ b/photoshop/api/_layerSets.py @@ -1,62 +1,115 @@ +"""The collection of layer sets in the document.""" + # Import third-party modules +from __future__ import annotations + +from typing import Any, Union, overload + from comtypes import ArgumentError # Import local modules -from photoshop.api._core import Photoshop +from photoshop.api._collection_base import CollectionBase from photoshop.api._layerSet import LayerSet from photoshop.api.errors import PhotoshopPythonAPIError -class LayerSets(Photoshop): - """The layer sets collection in the document.""" - - def __init__(self, parent): - super().__init__(parent=parent) - self._flag_as_method( - "add", - "item", - "removeAll", - ) +class LayerSets(CollectionBase[LayerSet]): + """The collection of layer sets in the document. + + This class represents all the layer sets (layer groups) in a Photoshop document. + It provides methods to: + - Add new layer sets + - Access layer sets by index or name + - Remove layer sets + - Iterate over layer sets + """ - def __len__(self): - return self.length + @overload + def __getitem__(self, key: int) -> LayerSet: + """Get layer set by index.""" - def __iter__(self): - for layer_set in self.app: - yield layer_set + @overload + def __getitem__(self, key: str) -> LayerSet: + """Get layer set by name.""" - def __getitem__(self, key: str): - """Access a given LayerSet using dictionary key lookup.""" + def __getitem__(self, key: Union[int, str]) -> LayerSet: + """Access a given LayerSet using dictionary key lookup. + + Args: + key: Either an integer index or a string name of the layer set + + Returns: + LayerSet: The requested layer set + + Raises: + PhotoshopPythonAPIError: If the layer set could not be found + TypeError: If the key type is not int or str + """ 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) + if isinstance(key, str): + # Use COM object's item method to get by name + return self._wrap_item(self.app.item(key)) + if isinstance(key, int): + return self._wrap_item(self.app[key]) + raise TypeError(f"Key must be int or str, not {type(key)}") + except (ArgumentError, IndexError): + name_str = f'named "{key}"' if isinstance(key, str) else f"at index {key}" + raise PhotoshopPythonAPIError(f"Could not find a layer set {name_str}") - def add(self): - return LayerSet(self.app.add()) + def add(self) -> LayerSet: + """Add a new layer set to the document. + + Returns: + LayerSet: The newly created layer set + """ + return self._wrap_item(self.app.add()) - def item(self, index: int) -> LayerSet: - return LayerSet(self.app.item(index)) + def item(self, key: Union[int, str]) -> LayerSet: + """Get a layer set by its index or name. + + Args: + key: Either an integer index or a string name of the layer set + + Returns: + LayerSet: The layer set at the specified index or with the specified name + + Raises: + PhotoshopPythonAPIError: If the layer set could not be found + """ + try: + return self._wrap_item(self.app.item(key)) + except ArgumentError: + name_str = f'named "{key}"' if isinstance(key, str) else f"at index {key}" + raise PhotoshopPythonAPIError(f"Could not find a layer set {name_str}") - def removeAll(self): + def removeAll(self) -> None: + """Delete all layer sets in the collection.""" 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}"') + """Get the first layer set with the specified name. + + Args: + name: The name of the layer set to find + + Returns: + LayerSet: The layer set with the specified name + + Raises: + PhotoshopPythonAPIError: If no layer set with the specified name is found + """ + try: + return self._wrap_item(self.app.item(name)) + except ArgumentError: + raise PhotoshopPythonAPIError(f'Could not find a layer set named "{name}"') + + def _wrap_item(self, item: Any) -> LayerSet: + """Wrap a COM layer set object in a LayerSet instance. + + Args: + item: The COM layer set object to wrap + + Returns: + LayerSet: The wrapped layer set + """ + return LayerSet(item) diff --git a/photoshop/api/_layers.py b/photoshop/api/_layers.py index dfe1d126..a3d3d47e 100644 --- a/photoshop/api/_layers.py +++ b/photoshop/api/_layers.py @@ -1,50 +1,55 @@ # Import local modules +from __future__ import annotations + from photoshop.api._artlayer import ArtLayer -from photoshop.api._core import Photoshop +from photoshop.api._collection_base import CollectionBase from photoshop.api.errors import PhotoshopPythonAPIError # pylint: disable=too-many-public-methods -class Layers(Photoshop): +class Layers(CollectionBase[ArtLayer]): """The layers collection in the document.""" - def __init__(self, parent): - super().__init__(parent=parent) - self._flag_as_method( - "add", - "item", - ) - - @property - def _layers(self): - return list(self.app) - - def __len__(self): - return self.length - - def __getitem__(self, key): - item = self._layers[key] - return ArtLayer(item) - - @property - def length(self): - return len(self._layers) - - def removeAll(self): + def removeAll(self) -> None: """Deletes all elements.""" - for layer in self.app: - ArtLayer(layer).remove() - - def item(self, index): - return ArtLayer(self.app.item(index)) - - def __iter__(self): - for layer in self._layers: - yield ArtLayer(layer) + for layer in self: + layer.remove() + + def item(self, index: int) -> ArtLayer: + """Get layer by index. + + Args: + index: The index of the layer to get + + Returns: + ArtLayer: The layer at the specified index + """ + return self._wrap_item(self.app.item(index)) def getByName(self, name: str) -> ArtLayer: - """Get the first element in the collection with the provided name.""" - for layer in self.app: + """Get the first element in the collection with the provided name. + + Args: + name: The name of the layer to find + + Returns: + ArtLayer: The first layer with the specified name + + Raises: + PhotoshopPythonAPIError: If no layer with the specified name is found + """ + for layer in self: if layer.name == name: - return ArtLayer(layer) - raise PhotoshopPythonAPIError("X") + return layer + raise PhotoshopPythonAPIError(f'Could not find a layer named "{name}"') + + def _wrap_item(self, item: Any) -> ArtLayer: + """Wrap a COM layer object in an ArtLayer instance. + + Args: + item: The COM layer object to wrap + + Returns: + ArtLayer: The wrapped layer + """ + return ArtLayer(item) diff --git a/photoshop/api/_measurement_log.py b/photoshop/api/_measurement_log.py index 1e2d9720..5e554d55 100644 --- a/photoshop/api/_measurement_log.py +++ b/photoshop/api/_measurement_log.py @@ -1,21 +1,23 @@ -# Import local modules -from photoshop.api._core import Photoshop - - -class MeasurementLog(Photoshop): - """The log of measurements taken.""" - - def __init__(self, parent): - super().__init__(parent=parent) - self._flag_as_method( - "exportMeasurements", - "deleteMeasurements", - ) - - def exportMeasurements(self, file_path: str, range_: int = None, data_point=None): - if data_point is None: - data_point = [] - self.app.exportMeasurements(file_path, range_, data_point) - - def deleteMeasurements(self, range_: int): - self.app.deleteMeasurements(range_) +# Import local modules +from __future__ import annotations + +from photoshop.api._core import Photoshop + + +class MeasurementLog(Photoshop): + """The log of measurements taken.""" + + def __init__(self, parent): + super().__init__(parent=parent) + self._flag_as_method( + "exportMeasurements", + "deleteMeasurements", + ) + + def exportMeasurements(self, file_path: str, range_: int = None, data_point=None): + if data_point is None: + data_point = [] + self.app.exportMeasurements(file_path, range_, data_point) + + def deleteMeasurements(self, range_: int): + self.app.deleteMeasurements(range_) diff --git a/photoshop/api/_notifier.py b/photoshop/api/_notifier.py index e0f651de..be2ab6fa 100644 --- a/photoshop/api/_notifier.py +++ b/photoshop/api/_notifier.py @@ -1,56 +1,58 @@ -""" -The collection of Notifier objects in the document. Access through the -Application.notifiers collection property. For example: -var notRef = app.notifiers.add("OnClickGoButton", eventFile) -Notifiers must be enabled using the Application.notifiersEnabled property - -""" -# Import built-in modules -from pathlib import Path - -# Import local modules -from photoshop.api._core import Photoshop - - -class Notifier(Photoshop): - def __init__(self, parent=None): - super().__init__() - self._flag_as_method( - "remove", - ) - - @property - def event(self): - """The event identifier, a four-character code or a unique string.""" - return self.app.event - - @property - def eventClass(self): - """The class identifier, a four-character code or a unique string. - - When an event applies to multiple types of objects, use this - propery to distinguish which object this notifier applies to. - For example, the Make event ("Mk ") can apply to - documents ("Dcmn"), channels ("Chnl") and other objects. - - """ - return self.app.eventClass - - @property - def eventFile(self) -> Path: - """The path to the file to execute when the event occurs and - activates the notifier.""" - return Path(self.app.eventFile) - - def remove(self): - """Deletes this object. - - You can also remove a Notifier object - from the Script Events Manager - drop-down list by deleting the file named - Script Events Manager.xml from the - Photoshop preferences folder. See Adobe - Photoshop CC help for more information. - - """ - return self.app.remove() +"""The collection of Notifier objects in the document. Access through the +Application.notifiers collection property. For example: +var notRef = app.notifiers.add("OnClickGoButton", eventFile) +Notifiers must be enabled using the Application.notifiersEnabled property + +""" +# Import built-in modules +from __future__ import annotations + +from pathlib import Path + +# Import local modules +from photoshop.api._core import Photoshop + + +class Notifier(Photoshop): + def __init__(self, parent=None): + super().__init__() + self._flag_as_method( + "remove", + ) + + @property + def event(self): + """The event identifier, a four-character code or a unique string.""" + return self.app.event + + @property + def eventClass(self): + """The class identifier, a four-character code or a unique string. + + When an event applies to multiple types of objects, use this + propery to distinguish which object this notifier applies to. + For example, the Make event ("Mk ") can apply to + documents ("Dcmn"), channels ("Chnl") and other objects. + + """ + return self.app.eventClass + + @property + def eventFile(self) -> Path: + """The path to the file to execute when the event occurs and + activates the notifier. + """ + return Path(self.app.eventFile) + + def remove(self): + """Deletes this object. + + You can also remove a Notifier object + from the Script Events Manager + drop-down list by deleting the file named + Script Events Manager.xml from the + Photoshop preferences folder. See Adobe + Photoshop CC help for more information. + + """ + return self.app.remove() diff --git a/photoshop/api/_notifiers.py b/photoshop/api/_notifiers.py index 3ecbcdc3..9f46aff0 100644 --- a/photoshop/api/_notifiers.py +++ b/photoshop/api/_notifiers.py @@ -7,50 +7,63 @@ ```javascript var notRef = app.notifiers.add("OnClickGoButton", eventFile) ``` - """ # Import built-in modules -from typing import Any -from typing import Optional +from __future__ import annotations + +from typing import Any, Optional # Import local modules -from photoshop.api._core import Photoshop +from photoshop.api._collection_base import CollectionBase from photoshop.api._notifier import Notifier -class Notifiers(Photoshop): - """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", - ) - - @property - def _notifiers(self) -> list: - return [n for n in self.app] - - def __len__(self): - return self.length +class Notifiers(CollectionBase[Notifier]): + """The collection of notifiers currently configured in Photoshop. + + This class represents all the notifiers configured in the Scripts Events Manager + menu in Photoshop. It provides methods to: + - Add new notifiers + - Remove all notifiers + - Access notifiers by index + - Iterate over notifiers + + Note: + Notifiers must be enabled using the Application.notifiersEnabled property. + """ - 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: Optional[Any] = None, + event_class: Optional[Any] = None, + ) -> Notifier: + """Add a new notifier. + + Args: + event: The event to listen for + event_file: The script file to execute when the event occurs + event_class: The event class + + Returns: + Notifier: The newly created notifier + """ self.parent.notifiersEnabled = True - return Notifier(self.app.add(event, event_file, event_class)) + return self._wrap_item(self.app.add(event, event_file, event_class)) - def removeAll(self): + def removeAll(self) -> None: + """Remove all notifiers and disable notifications.""" self.app.removeAll() self.parent.notifiersEnabled = False + + def _wrap_item(self, item: Any) -> Notifier: + """Wrap a COM notifier object in a Notifier instance. + + Args: + item: The COM notifier object to wrap + + Returns: + Notifier: The wrapped notifier + """ + return Notifier(item) diff --git a/photoshop/api/_preferences.py b/photoshop/api/_preferences.py index c4d57356..e52bd5db 100644 --- a/photoshop/api/_preferences.py +++ b/photoshop/api/_preferences.py @@ -1,148 +1,152 @@ -# Import built-in modules -from pathlib import Path - -# Import local modules -from photoshop.api._core import Photoshop - - -class Preferences(Photoshop): - """The application preference settings.""" - - def __init__(self, parent): - super().__init__(parent=parent) - - @property - def additionalPluginFolder(self): - """The path to an additional plug-in folder.""" - return Path(self.app.additionalPluginFolder) - - @property - def appendExtension(self): - return self.app.appendExtension - - @property - def askBeforeSavingLayeredTIFF(self) -> bool: - return self.app.askBeforeSavingLayeredTIFF - - @property - def autoUpdateOpenDocuments(self) -> bool: - """True to automatically update open documents.""" - return self.app.autoUpdateOpenDocuments - - @autoUpdateOpenDocuments.setter - def autoUpdateOpenDocuments(self, boolean: bool): - """True to automatically update open documents.""" - self.app.autoUpdateOpenDocuments = boolean - - @property - def beepWhenDone(self) -> bool: - """True to beep when a process.""" - return self.app.beepWhenDone - - @beepWhenDone.setter - def beepWhenDone(self, boolean): - self.app.beepWhenDone = boolean - - @property - def colorChannelsInColor(self): - """True to display component channels in the Channels palette in - color.""" - return self.app.colorChannelsInColor - - @colorChannelsInColor.setter - def colorChannelsInColor(self, value): - self.app.colorChannelsInColor = value - - @property - def colorPicker(self): - """The preferred color selection tool.""" - return self.app.colorPicker - - @colorPicker.setter - def colorPicker(self, value): - self.app.colorPicker = value - - @property - def columnGutter(self): - return self.app.columnGutter - - @columnGutter.setter - def columnGutter(self, value): - self.app.columnGutter = value - - @property - def columnWidth(self): - return self.app.columnWidth - - @columnWidth.setter - def columnWidth(self, value): - self.app.columnWidth = value - - @property - def createFirstSnapshot(self): - """Automatically make the first snapshot when a new document is - created.""" - return self.app.createFirstSnapshot - - @createFirstSnapshot.setter - def createFirstSnapshot(self, boolean): - self.app.createFirstSnapshot = boolean - - @property - def dynamicColorSliders(self): - return self.app.dynamicColorSliders - - @dynamicColorSliders.setter - def dynamicColorSliders(self, boolean): - self.app.dynamicColorSliders = boolean - - @property - def editLogItems(self) -> bool: - """The preferred level of detail in the history log.""" - return self.app.editLogItems - - @editLogItems.setter - def editLogItems(self, boolean: bool): - """The preferred level of detail in the history log. - - Valid only when useHistoryLog = True - - """ - self.app.editLogItems = boolean - - @property - def exportClipboard(self): - """Retain Photoshop contents on the clipboard after exit the app.""" - return self.app.exportClipboard - - @exportClipboard.setter - def exportClipboard(self, boolean: bool): - self.app.exportClipboard = boolean - - @property - def fontPreviewSize(self): - return self.app.fontPreviewSize - - @fontPreviewSize.setter - def fontPreviewSize(self, value): - self.app.fontPreviewSize = value - - @property - def fullSizePreview(self): - return self.app.fullSizePreview - - @fullSizePreview.setter - def fullSizePreview(self, value): - self.app.fullSizePreview = value - - @property - def gamutWarningOpacity(self): - return self.app.gamutWarningOpacity - - @property - def rulerUnits(self): - return self.app.rulerUnits - - @rulerUnits.setter - def rulerUnits(self, value): - self.app.rulerUnits = value +# Import built-in modules +from __future__ import annotations + +from pathlib import Path + +# Import local modules +from photoshop.api._core import Photoshop + + +class Preferences(Photoshop): + """The application preference settings.""" + + def __init__(self, parent): + super().__init__(parent=parent) + + @property + def additionalPluginFolder(self): + """The path to an additional plug-in folder.""" + return Path(self.app.additionalPluginFolder) + + @property + def appendExtension(self): + return self.app.appendExtension + + @property + def askBeforeSavingLayeredTIFF(self) -> bool: + return self.app.askBeforeSavingLayeredTIFF + + @property + def autoUpdateOpenDocuments(self) -> bool: + """True to automatically update open documents.""" + return self.app.autoUpdateOpenDocuments + + @autoUpdateOpenDocuments.setter + def autoUpdateOpenDocuments(self, boolean: bool): + """True to automatically update open documents.""" + self.app.autoUpdateOpenDocuments = boolean + + @property + def beepWhenDone(self) -> bool: + """True to beep when a process.""" + return self.app.beepWhenDone + + @beepWhenDone.setter + def beepWhenDone(self, boolean): + self.app.beepWhenDone = boolean + + @property + def colorChannelsInColor(self): + """True to display component channels in the Channels palette in + color. + """ + return self.app.colorChannelsInColor + + @colorChannelsInColor.setter + def colorChannelsInColor(self, value): + self.app.colorChannelsInColor = value + + @property + def colorPicker(self): + """The preferred color selection tool.""" + return self.app.colorPicker + + @colorPicker.setter + def colorPicker(self, value): + self.app.colorPicker = value + + @property + def columnGutter(self): + return self.app.columnGutter + + @columnGutter.setter + def columnGutter(self, value): + self.app.columnGutter = value + + @property + def columnWidth(self): + return self.app.columnWidth + + @columnWidth.setter + def columnWidth(self, value): + self.app.columnWidth = value + + @property + def createFirstSnapshot(self): + """Automatically make the first snapshot when a new document is + created. + """ + return self.app.createFirstSnapshot + + @createFirstSnapshot.setter + def createFirstSnapshot(self, boolean): + self.app.createFirstSnapshot = boolean + + @property + def dynamicColorSliders(self): + return self.app.dynamicColorSliders + + @dynamicColorSliders.setter + def dynamicColorSliders(self, boolean): + self.app.dynamicColorSliders = boolean + + @property + def editLogItems(self) -> bool: + """The preferred level of detail in the history log.""" + return self.app.editLogItems + + @editLogItems.setter + def editLogItems(self, boolean: bool): + """The preferred level of detail in the history log. + + Valid only when useHistoryLog = True + + """ + self.app.editLogItems = boolean + + @property + def exportClipboard(self): + """Retain Photoshop contents on the clipboard after exit the app.""" + return self.app.exportClipboard + + @exportClipboard.setter + def exportClipboard(self, boolean: bool): + self.app.exportClipboard = boolean + + @property + def fontPreviewSize(self): + return self.app.fontPreviewSize + + @fontPreviewSize.setter + def fontPreviewSize(self, value): + self.app.fontPreviewSize = value + + @property + def fullSizePreview(self): + return self.app.fullSizePreview + + @fullSizePreview.setter + def fullSizePreview(self, value): + self.app.fullSizePreview = value + + @property + def gamutWarningOpacity(self): + return self.app.gamutWarningOpacity + + @property + def rulerUnits(self): + return self.app.rulerUnits + + @rulerUnits.setter + def rulerUnits(self, value): + self.app.rulerUnits = value diff --git a/photoshop/api/_selection.py b/photoshop/api/_selection.py index 63e19e14..e4913ffa 100644 --- a/photoshop/api/_selection.py +++ b/photoshop/api/_selection.py @@ -1,194 +1,197 @@ -"""The selected area of the document or layer.""" - -# Import local modules -from photoshop.api._core import Photoshop -from photoshop.api.enumerations import ColorBlendMode -from photoshop.api.enumerations import SelectionType -from photoshop.api.solid_color import SolidColor - - -# pylint: disable=too-many-public-methods, too-many-arguments -class Selection(Photoshop): - """The selected area of the document.""" - - def __init__(self, parent=None): - super().__init__(parent=parent) - self._flag_as_method( - "clear", - "contract", - "copy", - "cut", - "deselect", - "expand", - "feather", - "fill", - "grow", - "invert", - "load", - "makeWorkPath", - "resize", - "resizeBoundary", - "rotate", - "rotateBoundary", - "select", - "selectBorder", - "similar", - "smooth", - "store", - "stroke", - "translate", - "translateBoundary", - ) - - @property - def bounds(self): - return self.app.bounds - - def parent(self): - return self.app.parent - - @property - def solid(self): - return self.app.solid - - @property - def typename(self): - return self.app.typename - - def clear(self): - """Clears the selection and does not copy it to the clipboard.""" - self.app.clear() - - def contract(self, contract_by): - """Contracts the selection.""" - self.app.contract(contract_by) - - def copy(self): - """Copies the selection to the clipboard.""" - self.app.copy() - - def cut(self): - """Cuts the current selection to the clipboard.""" - self.app.cut() - - def select(self, *args, **kwargs): - return self.app.select(*args, **kwargs) - - def deselect(self): - """Deselects the current selection.""" - return self.app.deselect() - - def expand(self, by: int): - """Expands the selection. - - Args: - by: The amount to expand the selection. - - """ - self.app.expand(by) - - def feather(self, by: int): - """Feathers the edges of the selection. - - Args: - by: The amount to feather the edge. - - """ - return self.app.feather(by) - - def fill( - self, - fill_type: SolidColor, - mode: ColorBlendMode = None, - opacity=None, - preserve_transparency=None, - ): - """Fills the selection.""" - return self.app.fill(fill_type, mode, opacity, preserve_transparency) - - def grow(self, tolerance, anti_alias): - """Grows the selection to include all adjacent pixels falling within - - The specified tolerance range. - - Args: - tolerance (int): The tolerance range. Range: 0 to 255. - anti_alias (bool): If true, anti-aliasing is used. - - - """ - return self.app.grow(tolerance, anti_alias) - - def invert(self): - """Inverts the selection.""" - self.app.invert() - - def load(self, from_channel, combination, inverting): - """Loads the selection from the specified channel.""" - return self.app.load(from_channel, combination, inverting) - - def makeWorkPath(self, tolerance): - """Makes this selection item the workpath for this document.""" - self.app.makeWorkPath(tolerance) - - def resize(self, horizontal, vertical, anchor): - """Resizes the selected area to the specified dimensions and anchor - position.""" - self.app.resize(horizontal, vertical, anchor) - - def resizeBoundary(self, horizontal, vertical, anchor): - """Scales the boundary of the selection.""" - self.app.resizeBoundary(horizontal, vertical, anchor) - - def rotate(self, angle, anchor): - """Rotates the object.""" - self.app.rotate(angle, anchor) - - def rotateBoundary(self, angle, anchor): - """Rotates the boundary of the selection.""" - self.app.rotateBoundary(angle, anchor) - - def stroke(self, strokeColor, width, location, mode, opacity, preserveTransparency): - """Strokes the selection. - - Args: - strokeColor (SolidColor): The color to stroke the selection with. - width (int): The stroke width. - location (int): The stroke location. - mode (int): The color blend mode. - opacity (int): The opacity of the stroke color as a percentage. - Range: 1 to 100. - preserveTransparency (bool): If true, preserves transparency. - - """ - return self.app.stroke(strokeColor, width, location, mode, opacity, preserveTransparency) - - def selectBorder(self, width): - """Selects the selection border only (in the specified width); - subsequent actions do not affect the selected area within the borders. - - Args: - width (int): The width of the border selection. - - """ - return self.app.selectBorder(width) - - def similar(self, tolerance, antiAlias): - return self.app.similar(tolerance, antiAlias) - - def smooth(self, radius): - """Cleans up stray pixels left inside or outside a color-based - selection (within the radius specified in pixels).""" - return self.app.smooth(radius) - - def store(self, into, combination=SelectionType.ReplaceSelection): - """Saves the selection as a channel.""" - return self.app.store(into, combination) - - def translate(self, deltaX, deltaY): - """Moves the object relative to its current position.""" - return self.app.translate(deltaX, deltaY) - - def translateBoundary(self, deltaX, deltaY): - """Moves the boundary of selection relative to its current position.""" - return self.app.translateBoundary(deltaX, deltaY) +"""The selected area of the document or layer.""" + +# Import local modules +from __future__ import annotations + +from photoshop.api._core import Photoshop +from photoshop.api.enumerations import ColorBlendMode, SelectionType +from photoshop.api.solid_color import SolidColor + + +# pylint: disable=too-many-public-methods, too-many-arguments +class Selection(Photoshop): + """The selected area of the document.""" + + def __init__(self, parent=None): + super().__init__(parent=parent) + self._flag_as_method( + "clear", + "contract", + "copy", + "cut", + "deselect", + "expand", + "feather", + "fill", + "grow", + "invert", + "load", + "makeWorkPath", + "resize", + "resizeBoundary", + "rotate", + "rotateBoundary", + "select", + "selectBorder", + "similar", + "smooth", + "store", + "stroke", + "translate", + "translateBoundary", + ) + + @property + def bounds(self): + return self.app.bounds + + def parent(self): + return self.app.parent + + @property + def solid(self): + return self.app.solid + + @property + def typename(self): + return self.app.typename + + def clear(self): + """Clears the selection and does not copy it to the clipboard.""" + self.app.clear() + + def contract(self, contract_by): + """Contracts the selection.""" + self.app.contract(contract_by) + + def copy(self): + """Copies the selection to the clipboard.""" + self.app.copy() + + def cut(self): + """Cuts the current selection to the clipboard.""" + self.app.cut() + + def select(self, *args, **kwargs): + return self.app.select(*args, **kwargs) + + def deselect(self): + """Deselects the current selection.""" + return self.app.deselect() + + def expand(self, by: int): + """Expands the selection. + + Args: + by: The amount to expand the selection. + + """ + self.app.expand(by) + + def feather(self, by: int): + """Feathers the edges of the selection. + + Args: + by: The amount to feather the edge. + + """ + return self.app.feather(by) + + def fill( + self, + fill_type: SolidColor, + mode: ColorBlendMode = None, + opacity=None, + preserve_transparency=None, + ): + """Fills the selection.""" + return self.app.fill(fill_type, mode, opacity, preserve_transparency) + + def grow(self, tolerance, anti_alias): + """Grows the selection to include all adjacent pixels falling within + + The specified tolerance range. + + Args: + tolerance (int): The tolerance range. Range: 0 to 255. + anti_alias (bool): If true, anti-aliasing is used. + + + """ + return self.app.grow(tolerance, anti_alias) + + def invert(self): + """Inverts the selection.""" + self.app.invert() + + def load(self, from_channel, combination, inverting): + """Loads the selection from the specified channel.""" + return self.app.load(from_channel, combination, inverting) + + def makeWorkPath(self, tolerance): + """Makes this selection item the workpath for this document.""" + self.app.makeWorkPath(tolerance) + + def resize(self, horizontal, vertical, anchor): + """Resizes the selected area to the specified dimensions and anchor + position. + """ + self.app.resize(horizontal, vertical, anchor) + + def resizeBoundary(self, horizontal, vertical, anchor): + """Scales the boundary of the selection.""" + self.app.resizeBoundary(horizontal, vertical, anchor) + + def rotate(self, angle, anchor): + """Rotates the object.""" + self.app.rotate(angle, anchor) + + def rotateBoundary(self, angle, anchor): + """Rotates the boundary of the selection.""" + self.app.rotateBoundary(angle, anchor) + + def stroke(self, strokeColor, width, location, mode, opacity, preserveTransparency): + """Strokes the selection. + + Args: + strokeColor (SolidColor): The color to stroke the selection with. + width (int): The stroke width. + location (int): The stroke location. + mode (int): The color blend mode. + opacity (int): The opacity of the stroke color as a percentage. + Range: 1 to 100. + preserveTransparency (bool): If true, preserves transparency. + + """ + return self.app.stroke(strokeColor, width, location, mode, opacity, preserveTransparency) + + def selectBorder(self, width): + """Selects the selection border only (in the specified width); + subsequent actions do not affect the selected area within the borders. + + Args: + width (int): The width of the border selection. + + """ + return self.app.selectBorder(width) + + def similar(self, tolerance, antiAlias): + return self.app.similar(tolerance, antiAlias) + + def smooth(self, radius): + """Cleans up stray pixels left inside or outside a color-based + selection (within the radius specified in pixels). + """ + return self.app.smooth(radius) + + def store(self, into, combination=SelectionType.ReplaceSelection): + """Saves the selection as a channel.""" + return self.app.store(into, combination) + + def translate(self, deltaX, deltaY): + """Moves the object relative to its current position.""" + return self.app.translate(deltaX, deltaY) + + def translateBoundary(self, deltaX, deltaY): + """Moves the boundary of selection relative to its current position.""" + return self.app.translateBoundary(deltaX, deltaY) diff --git a/photoshop/api/_text_fonts.py b/photoshop/api/_text_fonts.py index dbe43d63..47d710e7 100644 --- a/photoshop/api/_text_fonts.py +++ b/photoshop/api/_text_fonts.py @@ -1,42 +1,46 @@ # Import built-in modules -from typing import Any -from typing import Union +from __future__ import annotations + +from typing import Any, Union # 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._collection_base import CollectionBase from photoshop.api.errors import PhotoshopPythonAPIError from photoshop.api.text_font import TextFont -class TextFonts(Photoshop): - """An installed font.""" - - def __init__(self, parent=None): - super().__init__(parent=parent) - +class TextFonts(CollectionBase[TextFont]): + """The collection of installed fonts in Photoshop. + + This class represents all the fonts installed in the system and available to + Photoshop. It provides methods to: + - Access fonts by name or postScriptName + - Check if a font is installed + - Iterate over all available fonts + """ + """ MAGIC METHODS """ - - def __len__(self): + + def __len__(self) -> int: return self.length def __iter__(self): for font in self.app: - yield TextFont(font) + yield self._wrap_item(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: - name: Name or postScriptName of the font to look for. - + name: Name or postScriptName of the font to look for + Returns: - bool: True if font is found, otherwise False. + bool: True if font is found, otherwise False """ # Look for postScriptName if self.get(name): @@ -50,67 +54,73 @@ 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: key: The postScriptName of the font. - + Returns: TextFont instance. - + """ try: - return TextFont(self.app[key]) + return self._wrap_item(self.app[key]) except ArgumentError: raise PhotoshopPythonAPIError(f'Could not find a font with postScriptName "{key}"') """ METHODS """ - + def get(self, key: str, default: Any = None) -> Union[TextFont, Any]: - """ - Accesses a given TextFont using dictionary key lookup of postScriptName, returns default if not found. - + """Get a font by its postScriptName, return default if not found. + Args: - key: The postScriptName of the font. - default: Value to return if font isn't found. - + key: The postScriptName of the font + default: Value to return if font isn't found + Returns: - TextFont instance. - + TextFont: The font if found, otherwise the default value """ try: - return TextFont(self.app[key]) + return self._wrap_item(self.app[key]) except (KeyError, ArgumentError): return default def getByName(self, name: str) -> TextFont: - """Gets the font by the font name. - + """Get a font by its display name. + Args: - name: The name of the font. - - + name: The display name of the font + Returns: - font instance. - + TextFont: The font with the specified name + + Raises: + PhotoshopPythonAPIError: If no font with the specified name is found """ - for font in self.app: + for font in self: if font.name == name: - return TextFont(font) - raise PhotoshopPythonAPIError('Could not find a TextFont named "{name}"') + return font + raise PhotoshopPythonAPIError(f'Could not find a font named "{name}"') + + def _wrap_item(self, item: Any) -> TextFont: + """Wrap a COM font object in a TextFont instance. + + Args: + item: The COM font object to wrap + + Returns: + TextFont: The wrapped font + """ + return TextFont(item) """ 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(self.app) diff --git a/photoshop/api/action_descriptor.py b/photoshop/api/action_descriptor.py index 6ecd4b0b..d83c68d6 100644 --- a/photoshop/api/action_descriptor.py +++ b/photoshop/api/action_descriptor.py @@ -1,250 +1,254 @@ -"""A record of key-text_font pairs for actions. - -such as those included on the Adobe Photoshop Actions menu. -The ActionDescriptor class is part of the Action -Manager functionality. For more details on the Action Manager, -see the Photoshop Scripting Guide. - -""" - -# Import built-in modules -from pathlib import Path - -# Import local modules -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 - - -class ActionDescriptor(Photoshop): - """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. - For more details on the Action Manager, see the Photoshop Scripting Guide. - - """ - - object_name = "ActionDescriptor" - - def __init__(self): - super().__init__() - 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", - "putClass", - "putData", - "putDouble", - "putEnumerated", - "putInteger", - "putLargeInteger", - "putList", - "putObject", - "putPath", - "putReference", - "putString", - "putUnitDouble", - "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): - """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. - - """ - 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: - """Determines whether the descriptor is the same as another descriptor. - - Args: - otherDesc (.action_descriptor.ActionDescriptor): - - """ - return self.app.isEqual(otherDesc) - - def putBoolean(self, key: int, value: bool): - """Sets the value for a key whose type is boolean.""" - self.app.putBoolean(key, value) - - def putClass(self, key: int, value: int): - """Sets the value for a key whose type is class.""" - self.app.putClass(key, value) - - def putData(self, key: int, value: str): - """Puts raw byte data as a string value.""" - self.app.putData(key, value) - - def putDouble(self, key: int, value: float): - """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): - """Sets the enumeration type and value for a key.""" - self.app.putEnumerated(key, enum_type, value) - - def putInteger(self, key: int, value: int): - """Sets the value for a key whose type is integer.""" - self.app.putInteger(key, value) - - def putLargeInteger(self, key: int, value: int): - """Sets the value for a key whose type is large integer.""" - self.app.putLargeInteger(key, value) - - def putList(self, key: int, value: ActionList): - """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): - """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): - """Sets the value for a key whose type is path.""" - self.app.putPath(key, value) - - def putReference(self, key: int, value: ActionReference): - """Sets the value for a key whose type is an object reference.""" - self.app.putReference(key, value) - - def putString(self, key: int, value: str): - """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): - """Sets the value for a key whose type is a unit value formatted as - double.""" - self.app.putUnitDouble(key, unit_id, value) - - def toStream(self) -> str: - """Gets the entire descriptor as as stream of bytes, - for writing to disk.""" - return self.app.toSteadm() +"""A record of key-text_font pairs for actions. + +such as those included on the Adobe Photoshop Actions menu. +The ActionDescriptor class is part of the Action +Manager functionality. For more details on the Action Manager, +see the Photoshop Scripting Guide. + +""" + +# Import built-in modules +from __future__ import annotations + +from pathlib import Path + +# Import local modules +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 + + +class ActionDescriptor(Photoshop): + """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. + For more details on the Action Manager, see the Photoshop Scripting Guide. + + """ + + object_name = "ActionDescriptor" + + def __init__(self): + super().__init__() + 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", + "putClass", + "putData", + "putDouble", + "putEnumerated", + "putInteger", + "putLargeInteger", + "putList", + "putObject", + "putPath", + "putReference", + "putString", + "putUnitDouble", + "toStream", + ) + + @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 erase(self, key: int) -> None: + """Erases a key form the descriptor.""" + self.app.erase(key) + + 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) -> 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: + """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: + """Determines whether the descriptor is the same as another descriptor. + + Args: + otherDesc (.action_descriptor.ActionDescriptor): + + """ + return self.app.isEqual(otherDesc) + + 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) -> None: + """Sets the value for a key whose type is class.""" + self.app.putClass(key, value) + + 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) -> 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) -> None: + """Sets the enumeration type and value for a key.""" + self.app.putEnumerated(key, enum_type, value) + + 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) -> None: + """Sets the value for a key whose type is large integer.""" + self.app.putLargeInteger(key, value) + + 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) -> 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) -> None: + """Sets the value for a key whose type is path.""" + self.app.putPath(key, value) + + 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) -> 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) -> None: + """Sets the value for a key whose type is a unit value formatted as + double. + """ + self.app.putUnitDouble(key, unit_id, value) + + 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_list.py b/photoshop/api/action_list.py index b8c1e3f8..335d3907 100644 --- a/photoshop/api/action_list.py +++ b/photoshop/api/action_list.py @@ -1,69 +1,71 @@ -"""This object provides an array-style mechanism for storing data. - -It can be used for low-level access info Photoshop. - - -""" -# Import local modules -from photoshop.api._core import Photoshop - - -class ActionList(Photoshop): - """The list of commands that comprise an Action. - - (such as an Action created using the Actions palette in the Adobe Photoshop application). - The action list object is part of the Action Manager functionality. - For details on using the Action Manager, see the Photoshop Scripting Guide. - - """ - - object_name = "ActionList" - - def __init__(self, parent=None): - super().__init__(parent=parent) - self._flag_as_method( - "getBoolean", - "getClass", - "getData", - "getDouble", - "getEnumerationType", - "getEnumerationValue", - "getInteger", - "getLargeInteger", - "getList", - "getObjectType", - ) - - @property - def count(self): - return self.app.count - - def getBoolean(self, index): - return self.app.getBoolean(index) - - def getClass(self, index): - return self.app.getClass(index) - - def getData(self, index): - return self.app.getData(index) - - def getDouble(self, index): - return self.app.getDouble(index) - - def getEnumerationType(self, index): - return self.app.getEnumerationType(index) - - def getEnumerationValue(self, index): - return self.app.getEnumerationValue(index) - - def getInteger(self, index): - return self.app.getInteger(index) - - def getLargeInteger(self, index): - return self.app.getLargeInteger(index) - - def getList(self, index): - return self.app.getList(index) - - def getObjectType(self, index): - return self.app.getObjectType(index) +"""This object provides an array-style mechanism for storing data. + +It can be used for low-level access info Photoshop. + + +""" +# Import local modules +from __future__ import annotations + +from photoshop.api._core import Photoshop + + +class ActionList(Photoshop): + """The list of commands that comprise an Action. + + (such as an Action created using the Actions palette in the Adobe Photoshop application). + The action list object is part of the Action Manager functionality. + For details on using the Action Manager, see the Photoshop Scripting Guide. + + """ + + object_name = "ActionList" + + def __init__(self, parent=None): + super().__init__(parent=parent) + self._flag_as_method( + "getBoolean", + "getClass", + "getData", + "getDouble", + "getEnumerationType", + "getEnumerationValue", + "getInteger", + "getLargeInteger", + "getList", + "getObjectType", + ) + + @property + def count(self): + return self.app.count + + def getBoolean(self, index): + return self.app.getBoolean(index) + + def getClass(self, index): + return self.app.getClass(index) + + def getData(self, index): + return self.app.getData(index) + + def getDouble(self, index): + return self.app.getDouble(index) + + def getEnumerationType(self, index): + return self.app.getEnumerationType(index) + + def getEnumerationValue(self, index): + return self.app.getEnumerationValue(index) + + def getInteger(self, index): + return self.app.getInteger(index) + + def getLargeInteger(self, index): + return self.app.getLargeInteger(index) + + def getList(self, index): + return self.app.getList(index) + + def getObjectType(self, index): + return self.app.getObjectType(index) diff --git a/photoshop/api/action_reference.py b/photoshop/api/action_reference.py index 2e72e3a4..4bfe2bc5 100644 --- a/photoshop/api/action_reference.py +++ b/photoshop/api/action_reference.py @@ -1,91 +1,95 @@ -"""This object provides information about what the action is refering to. - -For example, when referring to the name of something you might use keyName. -The reference would also need to know what name you are referring to. -In this case you could use classDocument for the name of the document or -classLayer for the name of the layer. -It can be used for low-level access into Contains data associated -with an ActionDescriptor. - -""" -# Import local modules -from photoshop.api._core import Photoshop -from photoshop.api.enumerations import ReferenceFormType - - -class ActionReference(Photoshop): - """Contains data describing a referenced Action. - - The action reference object is part of the Action Manager functionality. - For details on using the Action Manager, see the Photoshop Scripting Guide. - - """ - - object_name = "ActionReference" - - def __init__(self, parent=None): - super().__init__(parent=parent) - self._flag_as_method( - "getContainer", - "getDesiredClass", - "getEnumeratedType", - "getEnumeratedValue", - "getForm", - "getIdentifier", - "getIndex", - "putName", - "putClass", - "putEnumerated", - "putIdentifier", - "putIndex", - "putOffset", - "putProperty", - ) - - def getContainer(self): - return self.app.getContainer() - - def getDesiredClass(self): - return self.app.getDesiredClass() - - def getEnumeratedType(self) -> int: - return self.app.getEnumeratedType() - - def getEnumeratedValue(self) -> int: - return self.app.getEnumeratedValue() - - def getForm(self) -> ReferenceFormType: - """Gets the form of this action reference.""" - return ReferenceFormType(self.app.getForm()) - - def getIdentifier(self) -> int: - """Gets the identifier value for a reference whose form is - identifier.""" - return self.app.getIdentifier() - - 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 putClass(self, value): - return self.app.putClass(value) - - def putEnumerated(self, desired_class, enum_type, value): - """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) - - def putIdentifier(self, desired_class, value): - return self.app.putIdentifier(desired_class, value) - - def putIndex(self, desired_class, value): - return self.app.putIndex(desired_class, value) - - def putOffset(self, desired_class, value): - return self.app.putOffset(desired_class, value) - - def putProperty(self, desired_class, value): - return self.app.putProperty(desired_class, value) +"""This object provides information about what the action is refering to. + +For example, when referring to the name of something you might use keyName. +The reference would also need to know what name you are referring to. +In this case you could use classDocument for the name of the document or +classLayer for the name of the layer. +It can be used for low-level access into Contains data associated +with an ActionDescriptor. + +""" +# Import local modules +from __future__ import annotations + +from photoshop.api._core import Photoshop +from photoshop.api.enumerations import ReferenceFormType + + +class ActionReference(Photoshop): + """Contains data describing a referenced Action. + + The action reference object is part of the Action Manager functionality. + For details on using the Action Manager, see the Photoshop Scripting Guide. + + """ + + object_name = "ActionReference" + + def __init__(self, parent=None): + super().__init__(parent=parent) + self._flag_as_method( + "getContainer", + "getDesiredClass", + "getEnumeratedType", + "getEnumeratedValue", + "getForm", + "getIdentifier", + "getIndex", + "putName", + "putClass", + "putEnumerated", + "putIdentifier", + "putIndex", + "putOffset", + "putProperty", + ) + + def getContainer(self): + return self.app.getContainer() + + def getDesiredClass(self): + return self.app.getDesiredClass() + + def getEnumeratedType(self) -> int: + return self.app.getEnumeratedType() + + def getEnumeratedValue(self) -> int: + return self.app.getEnumeratedValue() + + def getForm(self) -> ReferenceFormType: + """Gets the form of this action reference.""" + return ReferenceFormType(self.app.getForm()) + + def getIdentifier(self) -> int: + """Gets the identifier value for a reference whose form is + identifier. + """ + return self.app.getIdentifier() + + 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 putClass(self, value): + return self.app.putClass(value) + + def putEnumerated(self, desired_class, enum_type, value): + """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) + + def putIdentifier(self, desired_class, value): + return self.app.putIdentifier(desired_class, value) + + def putIndex(self, desired_class, value): + return self.app.putIndex(desired_class, value) + + def putOffset(self, desired_class, value): + return self.app.putOffset(desired_class, value) + + def putProperty(self, desired_class, value): + return self.app.putProperty(desired_class, value) diff --git a/photoshop/api/application.py b/photoshop/api/application.py index 0df16cfa..7aafd344 100644 --- a/photoshop/api/application.py +++ b/photoshop/api/application.py @@ -1,499 +1,486 @@ -"""The Adobe Photoshop CC application object. - -Which is the root of the object model and provides access to all other -objects. This object provides application-wide information, -such as application defaults and available fonts. It provides many important -methods, such as those for opening files and loading documents. - -app = Application() - -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 - -# Import local modules -from photoshop.api._artlayer import ArtLayer -from photoshop.api._core import Photoshop -from photoshop.api._document import Document -from photoshop.api._documents import Documents -from photoshop.api._layerSets import LayerSets -from photoshop.api._measurement_log import MeasurementLog -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.errors import PhotoshopPythonAPIError -from photoshop.api.solid_color import SolidColor - - -class Application(Photoshop): - """The Adobe Photoshop application object, which contains all other Adobe Photoshop objects. - - This is the root of the object model, and provides access to all other objects. - To access the properties and methods, you can use the pre-defined global variable app. - - """ - - def __init__(self, version: Optional[str] = None): - super().__init__(ps_version=version) - self._flag_as_method( - "batch", - "charIDToTypeID", - "doAction", - "doJavaScript", - "eraseCustomOptions", - "executeAction", - "executeActionGet", - "featureEnabled", - "getCustomOptions", - "isQuicktimeAvailable", - "load", - "open", - "openDialog", - "purge", - "putCustomOptions", - "refresh", - "stringIDToTypeID", - "toolSupportsBrushes", - "toolSupportsPresets", - "typeIDToCharID", - "typeIDToStringID", - ) - - @property - def activeLayer(self) -> ArtLayer: - return ArtLayer(self.app.ArtLayer) - - @property - def layerSets(self) -> LayerSets: - return LayerSets(self.app.LayerSets) - - @property - def activeDocument(self) -> Document: - """The front-most documents. - - Setting this property is equivalent to clicking an - open document in the Adobe Photoshop CC - application to bring it to the front of the screen. - - """ - return Document(self.app.activeDocument) - - @activeDocument.setter - def activeDocument(self, document: Document): - self.app.activeDocument = document - - @property - def backgroundColor(self) -> SolidColor: - """The default background color and color style for documents.""" - return SolidColor(self.app.backgroundColor) - - @backgroundColor.setter - def backgroundColor(self, color: SolidColor): - """Sets the default background color and color style for documents. - - Args: - color: The SolidColor instance. - - """ - self.app.backgroundColor = color - - @property - def build(self) -> str: - """str: The information about the application.""" - return self.app.build - - @property - def colorSettings(self) -> str: - """str: The name of the currently selected color settings profile - (selected with Edit > Color Settings). - - """ - return self.app.colorSettings - - @colorSettings.setter - def colorSettings(self, settings: str): - """Sets the currently selected color settings profile. - - Args: - settings: The name of a color settings profile to select. - - """ - try: - self.doJavaScript(f'app.colorSettings="{settings}"') - except COMError as e: - raise PhotoshopPythonAPIError(f"Invalid color profile provided: '{settings}'") from e - - @property - def currentTool(self) -> str: - """str: The name of the current tool selected.""" - return self.app.currentTool - - @currentTool.setter - def currentTool(self, tool_name: str): - """Sets the currently selected tool. - - Args: - tool_name: The name of a tool to select.. - - """ - self.app.currentTool = tool_name - - @property - def displayDialogs(self) -> DialogModes: - """The dialog mode for the document, which indicates whether - Photoshop displays dialogs when the script runs.""" - return DialogModes(self.app.displayDialogs) - - @displayDialogs.setter - def displayDialogs(self, dialog_mode: DialogModes): - """The dialog mode for the document, which indicates whether - Photoshop displays dialogs when the script runs. - """ - self.app.displayDialogs = dialog_mode - - @property - def documents(self) -> Documents: - """._documents.Documents: The Documents instance.""" - return Documents(self.app.documents) - - @property - def fonts(self) -> TextFonts: - return TextFonts(self.app.fonts) - - @property - def foregroundColor(self) -> SolidColor: - """Get default foreground color. - - Used to paint, fill, and stroke selections. - - Returns: - The SolidColor instance. - - """ - return SolidColor(parent=self.app.foregroundColor) - - @foregroundColor.setter - def foregroundColor(self, color: SolidColor): - """Set the `foregroundColor`. - - Args: - color: The SolidColor instance. - - """ - self.app.foregroundColor = color - - @property - def freeMemory(self) -> float: - """The amount of unused memory available to .""" - return self.app.freeMemory - - @property - def locale(self) -> str: - """The language locale of the application.""" - return self.app.locale - - @property - def macintoshFileTypes(self) -> List[str]: - """A list of the image file types Photoshop can open.""" - return self.app.macintoshFileTypes - - @property - def measurementLog(self) -> MeasurementLog: - """The log of measurements taken.""" - return MeasurementLog(self.app.measurementLog) - - @property - def name(self) -> str: - return self.app.name - - @property - def notifiers(self) -> Notifiers: - """The notifiers currently configured (in the Scripts Events Manager - menu in the application).""" - return Notifiers(self.app.notifiers) - - @property - def notifiersEnabled(self) -> bool: - """bool: If true, notifiers are enabled.""" - return self.app.notifiersEnabled - - @notifiersEnabled.setter - def notifiersEnabled(self, value: bool): - self.app.notifiersEnabled = value - - @property - def parent(self): - """The object’s container.""" - return self.app.parent - - @property - def path(self) -> Path: - """str: The full path to the location of the Photoshop application.""" - return Path(self.app.path) - - @property - def playbackDisplayDialogs(self): - return self.doJavaScript("app.playbackDisplayDialogs") - - @property - def playbackParameters(self): - """Stores and retrieves parameters used as part of a recorded action.""" - return self.app.playbackParameters - - @playbackParameters.setter - def playbackParameters(self, value): - self.app.playbackParameters = value - - @property - def preferences(self) -> Preferences: - return Preferences(self.app.preferences) - - @property - def preferencesFolder(self) -> Path: - return Path(self.app.preferencesFolder) - - @property - def recentFiles(self): - return self.app.recentFiles - - @property - def scriptingBuildDate(self): - return self.app.scriptingBuildDate - - @property - def scriptingVersion(self): - return self.app.scriptingVersion - - @property - def systemInformation(self): - return self.app.systemInformation - - @property - def version(self): - return self.app.version - - @property - def windowsFileTypes(self): - return self.app.windowsFileTypes - - # Methods. - def batch(self, files, actionName, actionSet, options): - """Runs the batch automation routine. - - Similar to the **File** > **Automate** > **Batch** command. - - """ - self.app.batch(files, actionName, actionSet, options) - - def beep(self): - """Causes a "beep" sound.""" - return self.eval_javascript("app.beep()") - - def bringToFront(self): - return self.eval_javascript("app.bringToFront()") - - def changeProgressText(self, text): - """Changes the text that appears in the progress window.""" - self.eval_javascript(f"app.changeProgressText('{text}')") - - def charIDToTypeID(self, char_id): - return self.app.charIDToTypeID(char_id) - - @staticmethod - def compareWithNumbers(first, second): - return first > second - - def doAction(self, action, action_from="Default Actions"): - """Plays the specified action from the Actions palette.""" - self.app.doAction(action, action_from) - return True - - def doForcedProgress(self, title, javascript): - script = "app.doForcedProgress('{}', '{}')".format( - title, - javascript, - ) - self.eval_javascript(script) - # Ensure the script execute success. - time.sleep(1) - - def doProgress(self, title, javascript): - """Performs a task with a progress bar. Other progress APIs must be - called periodically to update the progress bar and allow cancelling. - - Args: - title (str): String to show in the progress window. - javascript (str): JavaScriptString to execute. - - """ - script = "app.doProgress('{}', '{}')".format( - title, - javascript, - ) - self.eval_javascript(script) - # Ensure the script execute success. - time.sleep(1) - - def doProgressSegmentTask(self, segmentLength, done, total, javascript): - script = "app.doProgressSegmentTask({}, {}, {}, '{}');".format( - segmentLength, - done, - total, - javascript, - ) - self.eval_javascript(script) - # Ensure the script execute success. - time.sleep(1) - - def doProgressSubTask(self, index, limit, javascript): - script = "app.doProgressSubTask({}, {}, '{}');".format( - index, - limit, - javascript, - ) - self.eval_javascript(script) - # Ensure the script execute success. - time.sleep(1) - - def doProgressTask(self, index, javascript): - """Sections off a portion of the unused progress bar for execution of - a subtask. Returns false on cancel. - - """ - script = f"app.doProgressTask({index}, '{javascript}');" - self.eval_javascript(script) - # Ensure the script execute success. - time.sleep(1) - - def eraseCustomOptions(self, key): - """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 executeActionGet(self, reference): - return self.app.executeActionGet(reference) - - def featureEnabled(self, name): - """Determines whether the feature - - specified by name is enabled. - The following features are supported - - as values for name: - "photoshop/extended" - "photoshop/standard" - "photoshop/trial - - """ - return self.app.featureEnabled(name) - - def getCustomOptions(self, key): - """Retrieves user objects in the Photoshop registry for the ID with - value key.""" - return self.app.getCustomOptions(key) - - def open( - self, - document_file_path, - document_type: str = None, - as_smart_object: bool = False, - ) -> Document: - document = self.app.open(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: - """Loads a supported Photoshop document.""" - self.app.load(str(document_file_path)) - return self.activeDocument - - def doJavaScript(self, javascript, Arguments=None, ExecutionMode=None): - return self.app.doJavaScript(javascript, Arguments, ExecutionMode) - - def isQuicktimeAvailable(self) -> bool: - return self.app.isQuicktimeAvailable - - def openDialog(self): - return self.app.openDialog() - - def purge(self, target: PurgeTarget): - """Purges one or more caches. - - Args: - target: - 1: Clears the undo cache. - 2: Clears history states from the History palette. - 3: Clears the clipboard data. - 4: Clears all caches - - """ - self.app.purge(target) - - def putCustomOptions(self, key, custom_object, persistent): - self.app.putCustomOptions(key, custom_object, persistent) - - def refresh(self): - """Pauses the script while the application refreshes. - - Ues to slow down execution and show the results to the user as the - script runs. - Use carefully; your script runs much more slowly when using this - method. - - """ - self.app.refresh() - - def refreshFonts(self): - """Force the font list to get refreshed.""" - return self.eval_javascript("app.refreshFonts();") - - def runMenuItem(self, menu_id): - """Run a menu item given the menu ID.""" - return self.eval_javascript( - f"app.runMenuItem({menu_id})", - ) - - def showColorPicker(self): - """Returns false if dialog is cancelled, true otherwise.""" - return self.eval_javascript("app.showColorPicker();") - - def stringIDToTypeID(self, string_id): - return self.app.stringIDToTypeID(string_id) - - def togglePalettes(self): - """Toggle palette visibility.""" - return self.doJavaScript("app.togglePalettes()") - - def toolSupportsBrushes(self, tool): - return self.app.toolSupportsBrushes(tool) - - def toolSupportsBrushPresets(self, tool): - 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): - self.eval_javascript(f"app.updateProgress({done}, {total})") +"""The Adobe Photoshop CC application object. + +Which is the root of the object model and provides access to all other +objects. This object provides application-wide information, +such as application defaults and available fonts. It provides many important +methods, such as those for opening files and loading documents. + +app = Application() + +app.documents.add(800, 600, 72, "docRef") + +""" +# Import built-in modules +from __future__ import annotations + +import os +import time + +# Import third-party modules +from _ctypes import COMError +from pathlib import Path +from typing import List, Optional, Union + +# Import local modules +from photoshop.api._artlayer import ArtLayer +from photoshop.api._core import Photoshop +from photoshop.api._document import Document +from photoshop.api._documents import Documents +from photoshop.api._layerSets import LayerSets +from photoshop.api._measurement_log import MeasurementLog +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, PurgeTarget +from photoshop.api.errors import PhotoshopPythonAPIError +from photoshop.api.solid_color import SolidColor + + +class Application(Photoshop): + """The Adobe Photoshop application object, which contains all other Adobe Photoshop objects. + + This is the root of the object model, and provides access to all other objects. + To access the properties and methods, you can use the pre-defined global variable app. + + """ + + def __init__(self, version: Optional[str] = None): + super().__init__(ps_version=version) + self._flag_as_method( + "batch", + "charIDToTypeID", + "doAction", + "doJavaScript", + "eraseCustomOptions", + "executeAction", + "executeActionGet", + "featureEnabled", + "getCustomOptions", + "isQuicktimeAvailable", + "load", + "open", + "openDialog", + "purge", + "putCustomOptions", + "refresh", + "stringIDToTypeID", + "toolSupportsBrushes", + "toolSupportsPresets", + "typeIDToCharID", + "typeIDToStringID", + ) + + @property + def activeLayer(self) -> ArtLayer: + return ArtLayer(self.app.ArtLayer) + + @property + def layerSets(self) -> LayerSets: + return LayerSets(self.app.LayerSets) + + @property + def activeDocument(self) -> Document: + """The front-most documents. + + Setting this property is equivalent to clicking an + open document in the Adobe Photoshop CC + application to bring it to the front of the screen. + + """ + return Document(self.app.activeDocument) + + @activeDocument.setter + def activeDocument(self, document: Document): + self.app.activeDocument = document + + @property + def backgroundColor(self) -> SolidColor: + """The default background color and color style for documents.""" + return SolidColor(self.app.backgroundColor) + + @backgroundColor.setter + def backgroundColor(self, color: SolidColor): + """Sets the default background color and color style for documents. + + Args: + color: The SolidColor instance. + + """ + self.app.backgroundColor = color + + @property + def build(self) -> str: + """str: The information about the application.""" + return self.app.build + + @property + def colorSettings(self) -> str: + """str: The name of the currently selected color settings profile + (selected with Edit > Color Settings). + + """ + return self.app.colorSettings + + @colorSettings.setter + def colorSettings(self, settings: str): + """Sets the currently selected color settings profile. + + Args: + settings: The name of a color settings profile to select. + + """ + try: + self.doJavaScript(f'app.colorSettings="{settings}"') + except COMError as e: + raise PhotoshopPythonAPIError(f"Invalid color profile provided: '{settings}'") from e + + @property + def currentTool(self) -> str: + """str: The name of the current tool selected.""" + return self.app.currentTool + + @currentTool.setter + def currentTool(self, tool_name: str): + """Sets the currently selected tool. + + Args: + tool_name: The name of a tool to select.. + + """ + self.app.currentTool = tool_name + + @property + def displayDialogs(self) -> DialogModes: + """The dialog mode for the document, which indicates whether + Photoshop displays dialogs when the script runs. + """ + return DialogModes(self.app.displayDialogs) + + @displayDialogs.setter + def displayDialogs(self, dialog_mode: DialogModes): + """The dialog mode for the document, which indicates whether + Photoshop displays dialogs when the script runs. + """ + self.app.displayDialogs = dialog_mode + + @property + def documents(self) -> Documents: + """._documents.Documents: The Documents instance.""" + return Documents(self.app.documents) + + @property + def fonts(self) -> TextFonts: + return TextFonts(self.app.fonts) + + @property + def foregroundColor(self) -> SolidColor: + """Get default foreground color. + + Used to paint, fill, and stroke selections. + + Returns: + The SolidColor instance. + + """ + return SolidColor(parent=self.app.foregroundColor) + + @foregroundColor.setter + def foregroundColor(self, color: SolidColor): + """Set the `foregroundColor`. + + Args: + color: The SolidColor instance. + + """ + self.app.foregroundColor = color + + @property + def freeMemory(self) -> float: + """The amount of unused memory available to .""" + return self.app.freeMemory + + @property + def locale(self) -> str: + """The language locale of the application.""" + return self.app.locale + + @property + def macintoshFileTypes(self) -> List[str]: + """A list of the image file types Photoshop can open.""" + return self.app.macintoshFileTypes + + @property + def measurementLog(self) -> MeasurementLog: + """The log of measurements taken.""" + return MeasurementLog(self.app.measurementLog) + + @property + def name(self) -> str: + return self.app.name + + @property + def notifiers(self) -> Notifiers: + """The notifiers currently configured (in the Scripts Events Manager + menu in the application). + """ + return Notifiers(self.app.notifiers) + + @property + def notifiersEnabled(self) -> bool: + """bool: If true, notifiers are enabled.""" + return self.app.notifiersEnabled + + @notifiersEnabled.setter + def notifiersEnabled(self, value: bool): + self.app.notifiersEnabled = value + + @property + def parent(self): + """The object’s container.""" + return self.app.parent + + @property + def path(self) -> Path: + """str: The full path to the location of the Photoshop application.""" + return Path(self.app.path) + + @property + def playbackDisplayDialogs(self): + return self.doJavaScript("app.playbackDisplayDialogs") + + @property + def playbackParameters(self): + """Stores and retrieves parameters used as part of a recorded action.""" + return self.app.playbackParameters + + @playbackParameters.setter + def playbackParameters(self, value): + self.app.playbackParameters = value + + @property + def preferences(self) -> Preferences: + return Preferences(self.app.preferences) + + @property + def preferencesFolder(self) -> Path: + return Path(self.app.preferencesFolder) + + @property + def recentFiles(self): + return self.app.recentFiles + + @property + def scriptingBuildDate(self): + return self.app.scriptingBuildDate + + @property + def scriptingVersion(self): + return self.app.scriptingVersion + + @property + def systemInformation(self): + return self.app.systemInformation + + @property + def version(self): + return self.app.version + + @property + def windowsFileTypes(self): + return self.app.windowsFileTypes + + # Methods. + def batch(self, files, actionName, actionSet, options): + """Runs the batch automation routine. + + Similar to the **File** > **Automate** > **Batch** command. + + """ + self.app.batch(files, actionName, actionSet, options) + + def beep(self): + """Causes a "beep" sound.""" + return self.eval_javascript("app.beep()") + + def bringToFront(self): + return self.eval_javascript("app.bringToFront()") + + def changeProgressText(self, text): + """Changes the text that appears in the progress window.""" + self.eval_javascript(f"app.changeProgressText('{text}')") + + def charIDToTypeID(self, char_id): + return self.app.charIDToTypeID(char_id) + + @staticmethod + def compareWithNumbers(first, second): + return first > second + + def doAction(self, action, action_from="Default Actions"): + """Plays the specified action from the Actions palette.""" + self.app.doAction(action, action_from) + return True + + def doForcedProgress(self, title, javascript): + script = f"app.doForcedProgress('{title}', '{javascript}')" + self.eval_javascript(script) + # Ensure the script execute success. + time.sleep(1) + + def doProgress(self, title, javascript): + """Performs a task with a progress bar. Other progress APIs must be + called periodically to update the progress bar and allow cancelling. + + Args: + title (str): String to show in the progress window. + javascript (str): JavaScriptString to execute. + + """ + script = f"app.doProgress('{title}', '{javascript}')" + self.eval_javascript(script) + # Ensure the script execute success. + time.sleep(1) + + def doProgressSegmentTask(self, segmentLength, done, total, javascript): + script = f"app.doProgressSegmentTask({segmentLength}, {done}, {total}, '{javascript}');" + self.eval_javascript(script) + # Ensure the script execute success. + time.sleep(1) + + def doProgressSubTask(self, index, limit, javascript): + script = f"app.doProgressSubTask({index}, {limit}, '{javascript}');" + self.eval_javascript(script) + # Ensure the script execute success. + time.sleep(1) + + def doProgressTask(self, index, javascript): + """Sections off a portion of the unused progress bar for execution of + a subtask. Returns false on cancel. + + """ + script = f"app.doProgressTask({index}, '{javascript}');" + self.eval_javascript(script) + # Ensure the script execute success. + time.sleep(1) + + def eraseCustomOptions(self, key): + """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 executeActionGet(self, reference): + return self.app.executeActionGet(reference) + + def featureEnabled(self, name): + """Determines whether the feature + + specified by name is enabled. + The following features are supported + + as values for name: + "photoshop/extended" + "photoshop/standard" + "photoshop/trial + + """ + return self.app.featureEnabled(name) + + def getCustomOptions(self, key): + """Retrieves user objects in the Photoshop registry for the ID with + value key. + """ + return self.app.getCustomOptions(key) + + def open( + self, + document_file_path, + document_type: str = None, + as_smart_object: bool = False, + ) -> Document: + document = self.app.open(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: + """Loads a supported Photoshop document.""" + self.app.load(str(document_file_path)) + return self.activeDocument + + def doJavaScript(self, javascript, Arguments=None, ExecutionMode=None): + return self.app.doJavaScript(javascript, Arguments, ExecutionMode) + + def isQuicktimeAvailable(self) -> bool: + return self.app.isQuicktimeAvailable + + def openDialog(self): + return self.app.openDialog() + + def purge(self, target: PurgeTarget): + """Purges one or more caches. + + Args: + target: + 1: Clears the undo cache. + 2: Clears history states from the History palette. + 3: Clears the clipboard data. + 4: Clears all caches + + """ + self.app.purge(target) + + def putCustomOptions(self, key, custom_object, persistent): + self.app.putCustomOptions(key, custom_object, persistent) + + def refresh(self): + """Pauses the script while the application refreshes. + + Ues to slow down execution and show the results to the user as the + script runs. + Use carefully; your script runs much more slowly when using this + method. + + """ + self.app.refresh() + + def refreshFonts(self): + """Force the font list to get refreshed.""" + return self.eval_javascript("app.refreshFonts();") + + def runMenuItem(self, menu_id): + """Run a menu item given the menu ID.""" + return self.eval_javascript( + f"app.runMenuItem({menu_id})", + ) + + def showColorPicker(self): + """Returns false if dialog is cancelled, true otherwise.""" + return self.eval_javascript("app.showColorPicker();") + + def stringIDToTypeID(self, string_id): + return self.app.stringIDToTypeID(string_id) + + def togglePalettes(self): + """Toggle palette visibility.""" + return self.doJavaScript("app.togglePalettes()") + + def toolSupportsBrushes(self, tool): + return self.app.toolSupportsBrushes(tool) + + def toolSupportsBrushPresets(self, tool): + 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): + self.eval_javascript(f"app.updateProgress({done}, {total})") diff --git a/photoshop/api/batch_options.py b/photoshop/api/batch_options.py index 87156b24..7a3978bb 100644 --- a/photoshop/api/batch_options.py +++ b/photoshop/api/batch_options.py @@ -1,119 +1,122 @@ -# https://theiviaxx.github.io/photoshop-docs/Photoshop/BatchOptions.html -# Import local modules -from photoshop.api._core import Photoshop - - -class BatchOptions(Photoshop): - object_name = "BatchOptions" - - def __init__(self): - super().__init__() - - @property - def destination(self): - """The type of destination for the processed files.""" - return self.app.destination - - @destination.setter - def destination(self, value): - self.app.destination = value - - @property - def destinationFolder(self): - """The folder location for the processed files. Valid only when ‘destination’ = folder.""" - return self.app.destinationFolder - - @destinationFolder.setter - def destinationFolder(self, path): - self.app.destinationFolder = path - - @property - def errorFile(self): - """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 - - @errorFile.setter - def errorFile(self, file_path): - self.app.errorFile = file_path - - @property - def fileNaming(self) -> list: - """A list of file naming options. Maximum: 6.""" - return self.app.fileNaming - - @fileNaming.setter - def fileNaming(self, file_naming: list): - self.app.fileNaming = file_naming - - @property - def macintoshCompatible(self) -> bool: - """If true, the final file names are Macintosh compatible.""" - return self.app.macintoshCompatible - - @macintoshCompatible.setter - def macintoshCompatible(self, value: bool): - self.app.macintoshCompatible = value - - @property - def overrideOpen(self) -> bool: - """If true, overrides action open commands.""" - return self.app.overrideOpen - - @overrideOpen.setter - def overrideOpen(self, value: bool): - self.app.overrideOpen = value - - @property - def overrideSave(self) -> bool: - """If true, overrides save as action steps with the specified destination.""" - return self.app.overrideSave - - @overrideSave.setter - def overrideSave(self, value: bool): - self.app.overrideSave = value - - @property - def startingSerial(self) -> int: - """The starting serial number to use in naming files.""" - return self.app.startingSerial - - @startingSerial.setter - def startingSerial(self, value: int): - self.app.startingSerial = value - - @property - def suppressOpen(self) -> bool: - """If true, suppresses file open options dialogs.""" - return self.app.suppressOpen - - @suppressOpen.setter - def suppressOpen(self, value: bool): - self.app.suppressOpen = value - - @property - def suppressProfile(self) -> bool: - """If true, suppresses color profile warnings.""" - return self.app.suppressProfile - - @suppressProfile.setter - def suppressProfile(self, value: bool): - self.app.suppressProfile = value - - @property - def unixCompatible(self) -> bool: - """If true, the final file names are Unix compatible.""" - return self.app.unixCompatible - - @unixCompatible.setter - def unixCompatible(self, value: bool): - self.app.unixCompatible = value - - @property - def windowsCompatible(self) -> bool: - """If true, the final file names are Windows compatible.""" - return self.app.windowsCompatible - - @windowsCompatible.setter - def windowsCompatible(self, value: bool): - self.app.windowsCompatible = value +# https://theiviaxx.github.io/photoshop-docs/Photoshop/BatchOptions.html +# Import local modules +from __future__ import annotations + +from photoshop.api._core import Photoshop + + +class BatchOptions(Photoshop): + object_name = "BatchOptions" + + def __init__(self): + super().__init__() + + @property + def destination(self): + """The type of destination for the processed files.""" + return self.app.destination + + @destination.setter + def destination(self, value): + self.app.destination = value + + @property + def destinationFolder(self): + """The folder location for the processed files. Valid only when ‘destination’ = folder.""" + return self.app.destinationFolder + + @destinationFolder.setter + def destinationFolder(self, path): + self.app.destinationFolder = path + + @property + def errorFile(self): + """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 + + @errorFile.setter + def errorFile(self, file_path): + self.app.errorFile = file_path + + @property + def fileNaming(self) -> list: + """A list of file naming options. Maximum: 6.""" + return self.app.fileNaming + + @fileNaming.setter + def fileNaming(self, file_naming: list): + self.app.fileNaming = file_naming + + @property + def macintoshCompatible(self) -> bool: + """If true, the final file names are Macintosh compatible.""" + return self.app.macintoshCompatible + + @macintoshCompatible.setter + def macintoshCompatible(self, value: bool): + self.app.macintoshCompatible = value + + @property + def overrideOpen(self) -> bool: + """If true, overrides action open commands.""" + return self.app.overrideOpen + + @overrideOpen.setter + def overrideOpen(self, value: bool): + self.app.overrideOpen = value + + @property + def overrideSave(self) -> bool: + """If true, overrides save as action steps with the specified destination.""" + return self.app.overrideSave + + @overrideSave.setter + def overrideSave(self, value: bool): + self.app.overrideSave = value + + @property + def startingSerial(self) -> int: + """The starting serial number to use in naming files.""" + return self.app.startingSerial + + @startingSerial.setter + def startingSerial(self, value: int): + self.app.startingSerial = value + + @property + def suppressOpen(self) -> bool: + """If true, suppresses file open options dialogs.""" + return self.app.suppressOpen + + @suppressOpen.setter + def suppressOpen(self, value: bool): + self.app.suppressOpen = value + + @property + def suppressProfile(self) -> bool: + """If true, suppresses color profile warnings.""" + return self.app.suppressProfile + + @suppressProfile.setter + def suppressProfile(self, value: bool): + self.app.suppressProfile = value + + @property + def unixCompatible(self) -> bool: + """If true, the final file names are Unix compatible.""" + return self.app.unixCompatible + + @unixCompatible.setter + def unixCompatible(self, value: bool): + self.app.unixCompatible = value + + @property + def windowsCompatible(self) -> bool: + """If true, the final file names are Windows compatible.""" + return self.app.windowsCompatible + + @windowsCompatible.setter + def windowsCompatible(self, value: bool): + self.app.windowsCompatible = value diff --git a/photoshop/api/colors/__init__.py b/photoshop/api/colors/__init__.py index 61a01ac7..f59101f1 100644 --- a/photoshop/api/colors/__init__.py +++ b/photoshop/api/colors/__init__.py @@ -1,15 +1,16 @@ # Import local modules +from __future__ import annotations + from photoshop.api.colors.cmyk import CMYKColor from photoshop.api.colors.gray import GrayColor from photoshop.api.colors.hsb import HSBColor from photoshop.api.colors.lab import LabColor from photoshop.api.colors.rgb import RGBColor - __all__ = [ - CMYKColor.__name__, - GrayColor.__name__, - HSBColor.__name__, - LabColor.__name__, - RGBColor.__name__, + "CMYKColor", + "GrayColor", + "HSBColor", + "LabColor", + "RGBColor", ] diff --git a/photoshop/api/colors/cmyk.py b/photoshop/api/colors/cmyk.py index dbe260b8..3f83b1d2 100644 --- a/photoshop/api/colors/cmyk.py +++ b/photoshop/api/colors/cmyk.py @@ -1,49 +1,51 @@ -"""Defines a CMYK color, used in the `SolidColor` object.""" - -# Import local modules -from photoshop.api._core import Photoshop - - -class CMYKColor(Photoshop): - """A CMYK color specification.""" - - object_name = "CMYKColor" - - def __init__(self, parent): - super().__init__(parent=parent) - - @property - def black(self) -> int: - """The black color value. Range: 0.0 to 100.0.""" - return round(self.app.black) - - @black.setter - def black(self, value: int): - self.app.black = value - - @property - def cyan(self) -> int: - """The cyan color value. Range: 0.0 to 100.0.""" - return round(self.app.cyan) - - @cyan.setter - def cyan(self, value: int): - self.app.cyan = value - - @property - def magenta(self) -> int: - """The magenta color value. Range: 0.0 to 100.0.""" - return round(self.app.magenta) - - @magenta.setter - def magenta(self, value: int): - self.app.magenta = value - - @property - def yellow(self) -> int: - """The yellow color value. Range: 0.0 to 100.0.""" - return round(self.app.yellow) - - @yellow.setter - def yellow(self, value: int): - self.app.yellow = value +"""Defines a CMYK color, used in the `SolidColor` object.""" + +# Import local modules +from __future__ import annotations + +from photoshop.api._core import Photoshop + + +class CMYKColor(Photoshop): + """A CMYK color specification.""" + + object_name = "CMYKColor" + + def __init__(self, parent): + super().__init__(parent=parent) + + @property + def black(self) -> int: + """The black color value. Range: 0.0 to 100.0.""" + return round(self.app.black) + + @black.setter + def black(self, value: int): + self.app.black = value + + @property + def cyan(self) -> int: + """The cyan color value. Range: 0.0 to 100.0.""" + return round(self.app.cyan) + + @cyan.setter + def cyan(self, value: int): + self.app.cyan = value + + @property + def magenta(self) -> int: + """The magenta color value. Range: 0.0 to 100.0.""" + return round(self.app.magenta) + + @magenta.setter + def magenta(self, value: int): + self.app.magenta = value + + @property + def yellow(self) -> int: + """The yellow color value. Range: 0.0 to 100.0.""" + return round(self.app.yellow) + + @yellow.setter + def yellow(self, value: int): + self.app.yellow = value diff --git a/photoshop/api/colors/gray.py b/photoshop/api/colors/gray.py index b2321a02..8ab08b81 100644 --- a/photoshop/api/colors/gray.py +++ b/photoshop/api/colors/gray.py @@ -1,23 +1,25 @@ -"""Defines a gray color, used in the `SolidColor` object.""" - -# Import local modules -from photoshop.api._core import Photoshop - - -class GrayColor(Photoshop): - """Options for defining a gray color.""" - - object_name = "GrayColor" - - def __init__(self, parent): - super().__init__(parent=parent) - - @property - def gray(self) -> float: - """The gray value.""" - return self.app.gray - - @gray.setter - def gray(self, value: float): - """The gray value.""" - self.app.gray = value +"""Defines a gray color, used in the `SolidColor` object.""" + +# Import local modules +from __future__ import annotations + +from photoshop.api._core import Photoshop + + +class GrayColor(Photoshop): + """Options for defining a gray color.""" + + object_name = "GrayColor" + + def __init__(self, parent): + super().__init__(parent=parent) + + @property + def gray(self) -> float: + """The gray value.""" + return self.app.gray + + @gray.setter + def gray(self, value: float): + """The gray value.""" + self.app.gray = value diff --git a/photoshop/api/colors/hsb.py b/photoshop/api/colors/hsb.py index 76ead8c8..709c5826 100644 --- a/photoshop/api/colors/hsb.py +++ b/photoshop/api/colors/hsb.py @@ -1,38 +1,40 @@ -"""Defines an HSB color, used in the `SolidColor` object.""" - -# Import local modules -from photoshop.api._core import Photoshop - - -class HSBColor(Photoshop): - """An HSB color specification.""" - - object_name = "HSBColor" - - def __init__(self, parent): - super().__init__(parent=parent) - - @property - def brightness(self): - return round(self.app.brightness) - - @brightness.setter - def brightness(self, value): - self.app.brightness = value - - @property - def saturation(self): - return round(self.app.saturation) - - @saturation.setter - def saturation(self, value): - self.app.saturation = value - - @property - def hue(self): - """The hue value. Range: 0.0 to 360.0.""" - return round(self.app.hue) - - @hue.setter - def hue(self, value): - self.app.hue = value +"""Defines an HSB color, used in the `SolidColor` object.""" + +# Import local modules +from __future__ import annotations + +from photoshop.api._core import Photoshop + + +class HSBColor(Photoshop): + """An HSB color specification.""" + + object_name = "HSBColor" + + def __init__(self, parent): + super().__init__(parent=parent) + + @property + def brightness(self): + return round(self.app.brightness) + + @brightness.setter + def brightness(self, value): + self.app.brightness = value + + @property + def saturation(self): + return round(self.app.saturation) + + @saturation.setter + def saturation(self, value): + self.app.saturation = value + + @property + def hue(self): + """The hue value. Range: 0.0 to 360.0.""" + return round(self.app.hue) + + @hue.setter + def hue(self, value): + self.app.hue = value diff --git a/photoshop/api/colors/lab.py b/photoshop/api/colors/lab.py index 282493d9..39bea5f4 100644 --- a/photoshop/api/colors/lab.py +++ b/photoshop/api/colors/lab.py @@ -1,35 +1,37 @@ -# Import local modules -from photoshop.api._core import Photoshop - - -class LabColor(Photoshop): - """A Lab color specification.""" - - object_name = "LabColor" - - def __init__(self, parent): - super().__init__(parent=parent) - - @property - def A(self): - return round(self.app.A) - - @A.setter - def A(self, value): - self.app.A = value - - @property - def B(self): - return round(self.app.B) - - @B.setter - def B(self, value): - self.app.B = value - - @property - def L(self): - return round(self.app.L) - - @L.setter - def L(self, value): - self.app.L = value +# Import local modules +from __future__ import annotations + +from photoshop.api._core import Photoshop + + +class LabColor(Photoshop): + """A Lab color specification.""" + + object_name = "LabColor" + + def __init__(self, parent): + super().__init__(parent=parent) + + @property + def A(self): + return round(self.app.A) + + @A.setter + def A(self, value): + self.app.A = value + + @property + def B(self): + return round(self.app.B) + + @B.setter + def B(self, value): + self.app.B = value + + @property + def L(self): + return round(self.app.L) + + @L.setter + def L(self, value): + self.app.L = value diff --git a/photoshop/api/colors/rgb.py b/photoshop/api/colors/rgb.py index 2d6475bd..03fff3ff 100644 --- a/photoshop/api/colors/rgb.py +++ b/photoshop/api/colors/rgb.py @@ -1,49 +1,51 @@ -# Import local modules -from photoshop.api._core import Photoshop - - -class RGBColor(Photoshop): - """The definition of an RGB color mode.""" - - object_name = "RGBColor" - - def __init__(self, parent): - 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) - - @blue.setter - def blue(self, value: int): - self.app.blue = value - - @property - def green(self) -> int: - return round(self.app.green) - - @green.setter - def green(self, value: int): - self.app.green = value - - @property - def red(self) -> int: - return round(self.app.red) - - @red.setter - def red(self, value: int): - self.app.red = value - - @property - def hexValue(self): - return self.app.hexValue - - @hexValue.setter - def hexValue(self, value): - self.app.hexValue = value - - def __str__(self): - return f"[red: {self.red}, green:{self.green}, blue:{self.blue})]" +# Import local modules +from __future__ import annotations + +from photoshop.api._core import Photoshop + + +class RGBColor(Photoshop): + """The definition of an RGB color mode.""" + + object_name = "RGBColor" + + def __init__(self, parent): + 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) + + @blue.setter + def blue(self, value: int): + self.app.blue = value + + @property + def green(self) -> int: + return round(self.app.green) + + @green.setter + def green(self, value: int): + self.app.green = value + + @property + def red(self) -> int: + return round(self.app.red) + + @red.setter + def red(self, value: int): + self.app.red = value + + @property + def hexValue(self): + return self.app.hexValue + + @hexValue.setter + def hexValue(self, value): + self.app.hexValue = value + + def __str__(self): + return f"[red: {self.red}, green:{self.green}, blue:{self.blue})]" diff --git a/photoshop/api/constants.py b/photoshop/api/constants.py index 55166a0a..dd2b85d6 100644 --- a/photoshop/api/constants.py +++ b/photoshop/api/constants.py @@ -1,12 +1,14 @@ -# The Photoshop version to COM program ID mappings. -PHOTOSHOP_VERSION_MAPPINGS = { - "2025": "190", - "2024": "180", - "2023": "170", - "2022": "160", - "2021": "150", - "2020": "140", - "2019": "130", - "2018": "120", - "2017": "110", -} +# The Photoshop version to COM program ID mappings. +from __future__ import annotations + +PHOTOSHOP_VERSION_MAPPINGS = { + "2025": "190", + "2024": "180", + "2023": "170", + "2022": "160", + "2021": "150", + "2020": "140", + "2019": "130", + "2018": "120", + "2017": "110", +} diff --git a/photoshop/api/enumerations.py b/photoshop/api/enumerations.py index 4cefa755..83be6f28 100644 --- a/photoshop/api/enumerations.py +++ b/photoshop/api/enumerations.py @@ -1,1260 +1,1262 @@ -"""constants type of enum for Photoshop.""" -# Import built-in modules -from enum import IntEnum - - -class LensType(IntEnum): - MoviePrime = 5 - Prime105 = 3 - Prime35 = 2 - ZoomLens = 1 - - -class AdjustmentReference(IntEnum): - Absolute = 2 - Relative = 1 - - -class AnchorPosition(IntEnum): - BottomCenter = 8 - BottomLeft = 7 - BottomRight = 9 - MiddleCenter = 5 - MiddleLeft = 4 - MiddleRight = 6 - TopCenter = 2 - TopLeft = 1 - TopRight = 3 - - -class AntiAlias(IntEnum): - Crisp = 3 - NoAntialias = 1 - Sharp = 2 - Smooth = 5 - Strong = 4 - - -class AutoKernType(IntEnum): - Manual = 1 - Metrics = 2 - Optical = 3 - - -class BMPDepthType(IntEnum): - BMP16Bits = 16 - BMP1Bit = 1 - BMP24Bits = 24 - BMP32Bits = 32 - BMP4Bits = 4 - BMP8Bits = 8 - BMP_A1R5G5B5 = 61 - BMP_A4R4G4B4 = 64 - BMP_A8R8G8B8 = 67 - BMP_R5G6B5 = 62 - BMP_R8G8B8 = 65 - BMP_X1R5G5B5 = 60 - BMP_X4R4G4B4 = 63 - BMP_X8R8G8B8 = 66 - - -class BatchDestinationType(IntEnum): - Folder = 3 - NoDestination = 1 - SaveAndClose = 2 - - -class BitmapConversionType(IntEnum): - CustomPattern = 5 - DiffusionDither = 3 - HalfThreshold = 1 - HalftoneScreen = 4 - PatternDither = 2 - - -class BitmapHalfToneType(IntEnum): - HalftoneCross = 6 - HalftoneDiamond = 2 - HalftoneEllie = 3 - HalftoneLine = 4 - HalftoneRound = 1 - HalftoneSquare = 5 - - -class BitsPerChannelType(IntEnum): - Document16Bits = 16 - Document1Bit = 1 - Document32Bits = 32 - Document8Bits = 8 - - -class BlendMode(IntEnum): - ColorBlend = 22 - ColorBurn = 6 - ColorDodge = 10 - Darken = 4 - DarkerColor = 28 - Difference = 18 - Dissolve = 3 - Divide = 30 - Exclusion = 19 - HardLight = 14 - HardMix = 26 - Hue = 20 - Lighten = 8 - LighterColor = 27 - LinearBurn = 7 - LinearDodge = 11 - LinearLight = 16 - Luminosity = 23 - Multiply = 5 - NormalBlend = 2 - Overlay = 12 - PassThrough = 1 - PinLight = 17 - SaturationBlend = 21 - Screen = 9 - SoftLight = 13 - Subtract = 29 - VividLight = 15 - - -class ByteOrderType(IntEnum): - IBMByteOrder = 1 - MacOSByteOrder = 2 - - -class CameraRAWSettingsType(IntEnum): - CameraDefault = 0 - CustomSettings = 2 - SelectedImage = 1 - - -class CameraRAWSize(IntEnum): - ExtraLargeCameraRAW = 4 - LargeCameraRAW = 3 - MaximumCameraRAW = 5 - MediumCameraRAW = 2 - MinimumCameraRAW = 0 - SmallCameraRAW = 1 - - -class Case(IntEnum): - AllCa = 2 - NormalCase = 1 - SmallCa = 3 - - -class ChangeMode(IntEnum): - ConvertToBitmap = 5 - ConvertToCMYK = 3 - ConvertToGrayscale = 1 - ConvertToIndexedColor = 6 - ConvertToLab = 4 - ConvertToMultiChannel = 7 - ConvertToRGB = 2 - - -class ChannelType(IntEnum): - ComponentChannel = 1 - MaskedAreaAlphaChannel = 2 - SelectedAreaAlphaChannel = 3 - SpotColorChannel = 4 - - -class ColorBlendMode(IntEnum): - ColorBlendMode = 22 - BehindBlend = 24 - ClearBlend = 25 - ColorBurnBlend = 6 - ColorDodgeBlend = 10 - DarkenBlend = 4 - DarkerColorBlend = 28 - DifferenceBlend = 18 - DissolveBlend = 3 - DivideBlend = 30 - ExclusionBlend = 19 - HardLightBlend = 14 - HardMixBlend = 26 - HueBlend = 20 - LightenBlend = 8 - LighterColorBlend = 27 - LinearBurnBlend = 7 - LinearDodgeBlend = 11 - LinearLightBlend = 16 - LuminosityBlend = 23 - MultiplyBlend = 5 - NormalBlendColor = 2 - OverlayBlend = 12 - PinLightBlend = 17 - SaturationBlendColor = 21 - ScreenBlend = 9 - SoftLightBlend = 13 - SubtractBlend = 29 - VividLightBlend = 15 - - -class ColorModel(IntEnum): - CMYKModel = 3 - GrayscaleModel = 1 - HSBModel = 5 - LabModel = 4 - NoModel = 50 - RGBModel = 2 - - -class ColorPicker(IntEnum): - AdobeColorPicker = 1 - AppleColorPicker = 2 - PlugInColorPicker = 4 - WindowsColorPicker = 3 - - -class ColorProfileType(IntEnum): - Custom = 3 - No = 1 - Working = 2 - - -class ColorReductionType(IntEnum): - Adaptive = 2 - BlackWhiteReduction = 5 - CustomReduction = 4 - MacintoshColors = 7 - PerceptualReduction = 0 - Restrictive = 3 - SFWGrayscale = 6 - Selective = 1 - WindowsColors = 8 - - -class ColorSpaceType(IntEnum): - AdobeRGB = 0 - ColorMatchRGB = 1 - ProPhotoRGB = 2 - SRGB = 3 - - -class CopyrightedType(IntEnum): - CopyrightedWork = 1 - PublicDomain = 2 - Unmarked = 3 - - -class CreateFields(IntEnum): - Duplication = 1 - Interpolation = 2 - - -class CropToType(IntEnum): - ArtBox = 5 - BleedBox = 3 - BoundingBox = 0 - CropBox = 2 - MediaBox = 1 - TrimBox = 4 - - -class DCSType(IntEnum): - ColorComposite = 3 - GrayscaleComposite = 2 - NoComposite = 1 - - -class DepthMaource(IntEnum): - ImageHighlight = 4 - LayerMask = 3 - NoSource = 1 - TransparencyChannel = 2 - - -class DescValueType(IntEnum): - AliasType = 11 - BooleanType = 5 - ClassType = 10 - DoubleType = 2 - EnumeratedType = 8 - IntegerType = 1 - LargeIntegerType = 13 - ListType = 6 - ObjectType = 7 - RawType = 12 - ReferenceType = 9 - StringType = 4 - UnitDoubleType = 3 - - -class DialogModes(IntEnum): - DisplayAllDialogs = 1 - DisplayErrorDialogs = 2 - DisplayNoDialogs = 3 - - -class Direction(IntEnum): - Horizontal = 1 - Vertical = 2 - - -class DisplacementMapType(IntEnum): - StretchToFit = 1 - Tile = 2 - - -class DitherType(IntEnum): - Diffusion = 2 - NoDither = 1 - Noise = 4 - Pattern = 3 - - -class DocumentFill(IntEnum): - BackgroundColor = 2 - Transparent = 3 - White = 1 - - -class DocumentMode(IntEnum): - Bitmap = 5 - CMYK = 3 - Duotone = 8 - Grayscale = 1 - IndexedColor = 6 - Lab = 4 - MultiChannel = 7 - RGB = 2 - - -class EditLogItemsType(IntEnum): - Concise = 2 - Detailed = 3 - SessionOnly = 1 - - -class ElementPlacement(IntEnum): - PlaceAfter = 4 - PlaceAtBeginning = 1 - PlaceAtEnd = 2 - PlaceBefore = 3 - PlaceInside = 0 - - -class EliminateFields(IntEnum): - EvenFields = 2 - OddFields = 1 - - -class ExportType(IntEnum): - IllustratorPaths = 1 - SaveForWeb = 2 - - -class ExtensionType(IntEnum): - Lowercase = 2 - Uppercase = 3 - - -class FileNamingType(IntEnum): - Ddmm = 16 - Ddmmyy = 15 - DocumentNameLower = 2 - DocumentNameMixed = 1 - DocumentNameUpper = 3 - ExtensionLower = 17 - ExtensionUpper = 18 - Mmdd = 11 - Mmddyy = 10 - SerialLetterLower = 8 - SerialLetterUpper = 9 - SerialNumber1 = 4 - SerialNumber2 = 5 - SerialNumber3 = 6 - SerialNumber4 = 7 - Yyddmm = 14 - Yymmdd = 13 - Yyyymmdd = 12 - - -class FontPreviewType(IntEnum): - FontPreviewExtraLarge = 4 - FontPreviewHuge = 5 - FontPreviewLarge = 3 - FontPreviewMedium = 2 - FontPreviewNone = 0 - FontPreviewSmall = 1 - - -class ForcedColors(IntEnum): - BlackWhite = 2 - NoForced = 1 - Primaries = 3 - Web = 4 - - -class FormatOptionsType(IntEnum): - OptimizedBaseline = 2 - Progressive = 3 - StandardBaseline = 1 - - -class GalleryConstrainType(IntEnum): - ConstrainBoth = 3 - ConstrainHeight = 2 - ConstrainWidth = 1 - - -class GalleryFontType(IntEnum): - Arial = 1 - CourierNew = 2 - Helvetica = 3 - TimesNewRoman = 4 - - -class GallerySecurityTextColorType(IntEnum): - BlackText = 1 - CustomText = 3 - WhiteText = 2 - - -class GallerySecurityTextPositionType(IntEnum): - Centered = 1 - LowerLeft = 3 - LowerRight = 5 - UpperLeft = 2 - UpperRight = 4 - - -class GallerySecurityTextRotateType(IntEnum): - Clockwise45 = 2 - Clockwise90 = 3 - CounterClockwise45 = 4 - CounterClockwise90 = 5 - Zero = 1 - - -class GallerySecurityType(IntEnum): - Caption = 5 - Copyright = 4 - Credit = 6 - CustomSecurityText = 2 - Filename = 3 - NoSecurity = 1 - Title = 7 - - -class GalleryThumbSizeType(IntEnum): - CustomThumbnail = 4 - Large = 3 - Medium = 2 - Small = 1 - - -class Geometry(IntEnum): - Heptagon = 4 - Hexagon = 2 - Octagon = 5 - Pentagon = 1 - SquareGeometry = 3 - Triangle = 0 - - -class GridLineStyle(IntEnum): - GridDashedLine = 2 - GridDottedLine = 3 - GridSolidLine = 1 - - -class GridSize(IntEnum): - LargeGrid = 4 - MediumGrid = 3 - NoGrid = 1 - SmallGrid = 2 - - -class GuideLineStyle(IntEnum): - GuideDashedLine = 2 - GuideSolidLine = 1 - - -class IllustratorPathType(IntEnum): - AllPaths = 2 - DocumentBounds = 1 - NamedPath = 3 - - -class Intent(IntEnum): - AbsoluteColorimetric = 4 - Perceptual = 1 - RelativeColorimetric = 3 - Saturation = 2 - - -class JavaScriptExecutionMode(IntEnum): - BeforeRunning = 3 - DebuggerOnError = 2 - NeverShowDebugger = 1 - - -class Justification(IntEnum): - Center = 2 - CenterJustified = 5 - FullyJustified = 7 - Left = 1 - LeftJustified = 4 - Right = 3 - RightJustified = 6 - - -class Language(IntEnum): - BrazillianPortuguese = 13 - CanadianFrench = 4 - Danish = 17 - Dutch = 16 - EnglishUK = 2 - EnglishUSA = 1 - Finnish = 5 - French = 3 - German = 6 - Italian = 9 - Norwegian = 10 - NynorskNorwegian = 11 - OldGerman = 7 - Portuguese = 12 - Spanish = 14 - Swedish = 15 - SwissGerman = 8 - - -class LayerCompressionType(IntEnum): - RLELayerCompression = 1 - ZIPLayerCompression = 2 - - -class LayerKind(IntEnum): - BlackAndWhiteLayer = 22 - BrightnessContrastLayer = 9 - ChannelMixerLayer = 12 - ColorBalanceLayer = 8 - ColorLookup = 24 - CurvesLayer = 7 - ExposureLayer = 19 - GradientFillLayer = 4 - GradientMapLayer = 13 - HueSaturationLayer = 10 - InversionLayer = 14 - Layer3D = 20 - LevelsLayer = 6 - NormalLayer = 1 - PatternFillLayer = 5 - PhotoFilterLayer = 18 - PosterizeLayer = 16 - SelectiveColorLayer = 11 - SmartObjectLayer = 17 - SolidFillLayer = 3 - TextLayer = 2 - ThresholdLayer = 15 - Vibrance = 23 - VideoLayer = 21 - - -class LayerType(IntEnum): - ArtLayer = 1 - LayerSet = 2 - - -class MagnificationType(IntEnum): - ActualSize = 0 - FitPage = 1 - - -class MatteType(IntEnum): - BackgroundColorMatte = 3 - BlackMatte = 5 - ForegroundColorMatte = 2 - NetscapeGrayMatte = 7 - NoMatte = 1 - SemiGray = 6 - WhiteMatte = 4 - - -class MeasurementRange(IntEnum): - ActiveMeasurements = 2 - AllMeasurements = 1 - - -class MeasurementSource(IntEnum): - MeasureCountTool = 2 - MeasureRulerTool = 3 - MeasureSelection = 1 - - -class NewDocumentMode(IntEnum): - NewBitmap = 5 - NewCMYK = 3 - NewGray = 1 - NewLab = 4 - NewRGB = 2 - - -class NoiseDistribution(IntEnum): - GaussianNoise = 2 - UniformNoise = 1 - - -class OffsetUndefinedAreas(IntEnum): - OffsetRepeatEdgePixels = 3 - OffsetSetToLayerFill = 1 - OffsetWrapAround = 2 - - -class OpenDocumentMode(IntEnum): - OpenCMYK = 3 - OpenGray = 1 - OpenLab = 4 - OpenRGB = 2 - - -class OpenDocumentType(IntEnum): - AliasPIXOpen = 25 - BMPOpen = 2 - CameraRAWOpen = 32 - CompuServeGIFOpen = 3 - DICOMOpen = 33 - EOpen = 22 - EPICTPreviewOpen = 23 - ETIFFPreviewOpen = 24 - ElectricImageOpen = 26 - FilmstripOpen = 5 - JPEGOpen = 6 - PCXOpen = 7 - PDFOpen = 21 - PICTFileFormatOpen = 10 - PICTResourceFormatOpen = 11 - PNGOpen = 13 - PhotoCDOpen = 9 - PhotoshopDCS_1Open = 18 - PhotoshopDCS_2Open = 19 - PhotoshopEOpen = 4 - PhotoshopOpen = 1 - PhotoshopPDFOpen = 8 - PixarOpen = 12 - PortableBitmapOpen = 27 - RawOpen = 14 - SGIRGBOpen = 29 - ScitexCTOpen = 15 - SoftImageOpen = 30 - TIFFOpen = 17 - TargaOpen = 16 - WavefrontRLAOpen = 28 - WirelessBitmapOpen = 31 - - -class OperatingSystem(IntEnum): - OS2 = 1 - Windows = 2 - - -class Orientation(IntEnum): - Landscape = 1 - Portrait = 2 - - -class OtherPaintingCursors(IntEnum): - PreciseOther = 2 - StandardOther = 1 - - -class PDFCompatibilityType(IntEnum): - PDF13 = 1 - PDF14 = 2 - PDF15 = 3 - PDF16 = 4 - PDF17 = 5 - - -class PDFEncodingType(IntEnum): - PDFJPEG = 2 - PDFJPEG2000HIGH = 9 - PDFJPEG2000LOSSLESS = 14 - PDFJPEG2000LOW = 13 - PDFJPEG2000MED = 11 - PDFJPEG2000MEDHIGH = 10 - PDFJPEG2000MEDLOW = 12 - PDFJPEGHIGH = 4 - PDFJPEGLOW = 8 - PDFJPEGMED = 6 - PDFJPEGMEDHIGH = 5 - PDFJPEGMEDLOW = 7 - PDFNone = 0 - PDFZip = 1 - PDFZip4Bit = 3 - - -class PDFResampleType(IntEnum): - NoResample = 0 - PDFAverage = 1 - PDFBicubic = 3 - PDFSubSample = 2 - - -class PDFStandardType(IntEnum): - NoStandard = 0 - PDFX1A2001 = 1 - PDFX1A2003 = 2 - PDFX32002 = 3 - PDFX32003 = 4 - PDFX42008 = 5 - - -class PICTBitsPerPixel(IntEnum): - PICT16Bits = 16 - PICT2Bits = 2 - PICT32Bits = 32 - PICT4Bits = 4 - PICT8Bits = 8 - - -class PICTCompression(IntEnum): - JPEGHighPICT = 5 - JPEGLowPICT = 2 - JPEGMaximumPICT = 6 - JPEGMediumPICT = 4 - NoPICTCompression = 1 - - -class PaintingCursors(IntEnum): - BrushSize = 3 - Precise = 2 - Standard = 1 - - -class PaletteType(IntEnum): - Exact = 1 - LocalAdaptive = 8 - LocalPerceptual = 6 - LocalSelective = 7 - MacOSPalette = 2 - MasterAdaptive = 11 - MasterPerceptual = 9 - MasterSelective = 10 - PreviousPalette = 12 - Uniform = 5 - WebPalette = 4 - WindowsPalette = 3 - - -class PathKind(IntEnum): - ClippingPath = 2 - NormalPath = 1 - TextMask = 5 - VectorMask = 4 - WorkPath = 3 - - -class PhotoCDColorSpace(IntEnum): - Lab16 = 4 - Lab8 = 3 - RGB16 = 2 - RGB8 = 1 - - -class PhotoCDSize(IntEnum): - ExtraLargePhotoCD = 5 - LargePhotoCD = 4 - MaximumPhotoCD = 6 - MediumPhotoCD = 3 - MinimumPhotoCD = 1 - SmallPhotoCD = 2 - - -class PicturePackageTextType(IntEnum): - CaptionText = 5 - CopyrightText = 4 - CreditText = 6 - FilenameText = 3 - NoText = 1 - OriginText = 7 - UserText = 2 - - -class PointKind(IntEnum): - CornerPoint = 2 - SmoothPoint = 1 - - -class PointType(IntEnum): - PostScriptPoints = 1 - TraditionalPoints = 2 - - -class PolarConversionType(IntEnum): - PolarToRectangular = 2 - RectangularToPolar = 1 - - -class PreviewType(IntEnum): - EightBitTIFF = 3 - MonochromeTIFF = 2 - NoPreview = 1 - - -class PurgeTarget(IntEnum): - AllCaches = 4 - ClipboardCache = 3 - HistoryCaches = 2 - UndoCaches = 1 - - -class QueryStateType(IntEnum): - Always = 1 - Ask = 2 - Never = 3 - - -class RadialBlurMethod(IntEnum): - Spin = 1 - Zoom = 2 - - -class RadialBlurBest(IntEnum): - RadialBlurBest = 3 - RadialBlurDraft = 1 - RadialBlurGood = 2 - - -class RasterizeType(IntEnum): - EntireLayer = 5 - FillContent = 3 - LayerClippingPath = 4 - LinkedLayers = 6 - Shape = 2 - TextContents = 1 - - -class ReferenceFormType(IntEnum): - ReferenceClassType = 7 - ReferenceEnumeratedType = 5 - ReferenceIdentifierType = 3 - ReferenceIndexType = 2 - ReferenceNameType = 1 - ReferenceOffsetType = 4 - ReferencePropertyType = 6 - - -class ResampleMethod(IntEnum): - Automatic = 8 - Bicubic = 4 - BicubicAutomatic = 7 - BicubicSharper = 5 - BicubicSmoother = 6 - Bilinear = 3 - NearestNeighbor = 2 - NoResampling = 1 - PreserveDetails = 9 - - -class ResetTarget(IntEnum): - AllTools = 2 - AllWarnings = 1 - Everything = 3 - - -class RippleSize(IntEnum): - LargeRipple = 3 - MediumRipple = 2 - SmallRipple = 1 - - -class SaveBehavior(IntEnum): - AlwaysSave = 2 - AskWhenSaving = 3 - NeverSave = 1 - - -class SaveDocumentType(IntEnum): - AliasPIXSave = 25 - BMave = 2 - CompuServeGIFSave = 3 - ElectricImageSave = 26 - JPEGSave = 6 - PCXSave = 7 - PICTFileFormatSave = 10 - PICTResourceFormatSave = 11 - PNGSave = 13 - PhotoshopDCS_1Save = 18 - PhotoshopDCS_2Save = 19 - PhotoshopESave = 4 - PhotoshopPDFSave = 8 - Photoshoave = 1 - PixarSave = 12 - PortableBitmaave = 27 - RawSave = 14 - SGIRGBSave = 29 - ScitexCTSave = 15 - SoftImageSave = 30 - TIFFSave = 17 - TargaSave = 16 - WavefrontRLASave = 28 - WirelessBitmaave = 31 - - -class SaveEncoding(IntEnum): - Ascii = 3 - Binary = 1 - JPEGHigh = 5 - JPEGLow = 2 - JPEGMaximum = 6 - JPEGMedium = 4 - - -class SaveLogItemsType(IntEnum): - LogFile = 2 - LogFileAndMetadata = 3 - Metadata = 1 - - -class SaveOptions(IntEnum): - DoNotSaveChanges = 2 - PromptToSaveChanges = 3 - SaveChanges = 1 - - -class SelectionType(IntEnum): - DiminishSelection = 3 - ExtendSelection = 2 - IntersectSelection = 4 - ReplaceSelection = 1 - - -class ShapeOperation(IntEnum): - ShapeAdd = 1 - ShapeIntersect = 3 - ShapeSubtract = 4 - ShapeXOR = 2 - - -class SmartBlurMode(IntEnum): - SmartBlurEdgeOnly = 2 - SmartBlurNormal = 1 - SmartBlurOverlayEdge = 3 - - -class SmartBlurQuality(IntEnum): - SmartBlurHigh = 3 - SmartBlurLow = 1 - SmartBlurMedium = 2 - - -class SourceSpaceType(IntEnum): - DocumentSpace = 1 - ProofSpace = 2 - - -class SpherizeMode(IntEnum): - HorizontalSpherize = 2 - NormalSpherize = 1 - VerticalSpherize = 3 - - -class StrikeThruType(IntEnum): - StrikeBox = 3 - StrikeHeight = 2 - StrikeOff = 1 - - -class StrokeLocation(IntEnum): - CenterStroke = 2 - InsideStroke = 1 - OutsideStroke = 3 - - -class TargaBitsPerPixels(IntEnum): - Targa16Bits = 16 - Targa24Bits = 24 - Targa32Bits = 32 - - -class TextComposer(IntEnum): - AdobeEveryLine = 2 - AdobeSingleLine = 1 - - -class TextType(IntEnum): - ParagraphText = 2 - PointText = 1 - - -class TextureType(IntEnum): - BlocksTexture = 1 - CanvasTexture = 2 - FrostedTexture = 3 - TextureFile = 5 - TinyLensTexture = 4 - - -class TiffEncodingType(IntEnum): - NoTIFFCompression = 1 - TiffJPEG = 3 - TiffLZW = 2 - TiffZIP = 4 - - -class ToolType(IntEnum): - ArtHistoryBrush = 9 - BackgroundEraser = 4 - Blur = 11 - Brush = 2 - Burn = 14 - CloneStamp = 5 - ColorReplacementTool = 16 - Dodge = 13 - Eraser = 3 - HealingBrush = 7 - HistoryBrush = 8 - PatternStamp = 6 - Pencil = 1 - Sharpen = 12 - Smudge = 10 - Sponge = 15 - - -class TransitionType(IntEnum): - BlindsHorizontal = 1 - BlindsVertical = 2 - BoxIn = 4 - BoxOut = 5 - DissolveTransition = 3 - GlitterDown = 6 - GlitterRight = 7 - GlitterRightDown = 8 - NoTrasition = 9 - Random = 10 - SplitHorizontalIn = 11 - SplitHorizontalOut = 12 - SplitVerticalIn = 13 - SplitVerticalOut = 14 - WipeDown = 15 - WipeLeft = 16 - WipeRight = 17 - WipeUp = 18 - - -class TrimType(IntEnum): - BottomRightPixel = 9 - TopLeftPixel = 1 - TransparentPixels = 0 - - -class TypeUnits(IntEnum): - TypeMM = 4 - TypePixels = 1 - TypePoints = 5 - - -class UndefinedAreas(IntEnum): - RepeatEdgePixels = 2 - WrapAround = 1 - - -class UnderlineType(IntEnum): - UnderlineLeft = 3 - UnderlineOff = 1 - UnderlineRight = 2 - - -class Units(IntEnum): - CM = 3 - Inches = 2 - MM = 4 - Percent = 7 - Picas = 6 - Pixels = 1 - Points = 5 - - -class Urgency(IntEnum): - Four = 4 - High = 8 - Low = 1 - UrgencyNone = 0 - Normal = 5 - Seven = 7 - Six = 6 - Three = 3 - Two = 2 - - -class Wartyle(IntEnum): - Arc = 2 - ArcLower = 3 - ArcUpper = 4 - Arch = 5 - Bulge = 6 - Fish = 11 - FishEye = 13 - Flag = 9 - Inflate = 14 - NoWarp = 1 - Rise = 12 - ShellLower = 7 - ShellUpper = 8 - Squeeze = 15 - Twist = 16 - Wave = 10 - - -class WaveType(IntEnum): - Sine = 1 - Square = 3 - Triangular = 2 - - -class WhiteBalanceType(IntEnum): - AsShot = 0 - Auto = 1 - Cloudy = 3 - CustomCameraSettings = 8 - Daylight = 2 - Flash = 7 - Fluorescent = 6 - Shade = 4 - Tungsten = 5 - - -class ZigZagType(IntEnum): - AroundCenter = 1 - OutFromCenter = 2 - PondRipples = 3 - - -__all__ = [ - "LensType", - "AdjustmentReference", - "AnchorPosition", - "AntiAlias", - "AutoKernType", - "BMPDepthType", - "BatchDestinationType", - "BitmapConversionType", - "BitmapHalfToneType", - "BitsPerChannelType", - "BlendMode", - "ByteOrderType", - "CameraRAWSettingsType", - "CameraRAWSize", - "Case", - "ChangeMode", - "ChannelType", - "ColorBlendMode", - "ColorModel", - "ColorPicker", - "ColorProfileType", - "ColorReductionType", - "ColorSpaceType", - "CopyrightedType", - "CreateFields", - "CropToType", - "DCSType", - "DepthMaource", - "DescValueType", - "DialogModes", - "Direction", - "DisplacementMapType", - "DitherType", - "DocumentFill", - "DocumentMode", - "EditLogItemsType", - "ElementPlacement", - "EliminateFields", - "ExportType", - "ExtensionType", - "FileNamingType", - "FontPreviewType", - "ForcedColors", - "FormatOptionsType", - "GalleryConstrainType", - "GalleryFontType", - "GallerySecurityTextColorType", - "GallerySecurityTextPositionType", - "GallerySecurityTextRotateType", - "GallerySecurityType", - "GalleryThumbSizeType", - "Geometry", - "GridLineStyle", - "GridSize", - "GuideLineStyle", - "IllustratorPathType", - "Intent", - "JavaScriptExecutionMode", - "Justification", - "Language", - "LayerCompressionType", - "LayerKind", - "LayerType", - "MagnificationType", - "MatteType", - "MeasurementRange", - "MeasurementSource", - "NewDocumentMode", - "NoiseDistribution", - "OffsetUndefinedAreas", - "OpenDocumentMode", - "OpenDocumentType", - "OperatingSystem", - "Orientation", - "OtherPaintingCursors", - "PDFCompatibilityType", - "PDFEncodingType", - "PDFResampleType", - "PDFStandardType", - "PICTBitsPerPixel", - "PICTCompression", - "PaintingCursors", - "PaletteType", - "PathKind", - "PhotoCDColorSpace", - "PhotoCDSize", - "PicturePackageTextType", - "PointKind", - "PointType", - "PolarConversionType", - "PreviewType", - "PurgeTarget", - "QueryStateType", - "RadialBlurMethod", - "RadialBlurBest", - "RasterizeType", - "ReferenceFormType", - "ResampleMethod", - "ResetTarget", - "RippleSize", - "SaveBehavior", - "SaveDocumentType", - "SaveEncoding", - "SaveLogItemsType", - "SaveOptions", - "SelectionType", - "ShapeOperation", - "SmartBlurMode", - "SmartBlurQuality", - "SourceSpaceType", - "SpherizeMode", - "StrikeThruType", - "StrokeLocation", - "TargaBitsPerPixels", - "TextComposer", - "TextType", - "TextureType", - "TiffEncodingType", - "ToolType", - "TransitionType", - "TrimType", - "TypeUnits", - "UndefinedAreas", - "UnderlineType", - "Units", - "Urgency", - "Wartyle", - "WaveType", - "WhiteBalanceType", - "ZigZagType", -] +"""constants type of enum for Photoshop.""" +# Import built-in modules +from __future__ import annotations + +from enum import IntEnum + + +class LensType(IntEnum): + MoviePrime = 5 + Prime105 = 3 + Prime35 = 2 + ZoomLens = 1 + + +class AdjustmentReference(IntEnum): + Absolute = 2 + Relative = 1 + + +class AnchorPosition(IntEnum): + BottomCenter = 8 + BottomLeft = 7 + BottomRight = 9 + MiddleCenter = 5 + MiddleLeft = 4 + MiddleRight = 6 + TopCenter = 2 + TopLeft = 1 + TopRight = 3 + + +class AntiAlias(IntEnum): + Crisp = 3 + NoAntialias = 1 + Sharp = 2 + Smooth = 5 + Strong = 4 + + +class AutoKernType(IntEnum): + Manual = 1 + Metrics = 2 + Optical = 3 + + +class BMPDepthType(IntEnum): + BMP16Bits = 16 + BMP1Bit = 1 + BMP24Bits = 24 + BMP32Bits = 32 + BMP4Bits = 4 + BMP8Bits = 8 + BMP_A1R5G5B5 = 61 + BMP_A4R4G4B4 = 64 + BMP_A8R8G8B8 = 67 + BMP_R5G6B5 = 62 + BMP_R8G8B8 = 65 + BMP_X1R5G5B5 = 60 + BMP_X4R4G4B4 = 63 + BMP_X8R8G8B8 = 66 + + +class BatchDestinationType(IntEnum): + Folder = 3 + NoDestination = 1 + SaveAndClose = 2 + + +class BitmapConversionType(IntEnum): + CustomPattern = 5 + DiffusionDither = 3 + HalfThreshold = 1 + HalftoneScreen = 4 + PatternDither = 2 + + +class BitmapHalfToneType(IntEnum): + HalftoneCross = 6 + HalftoneDiamond = 2 + HalftoneEllie = 3 + HalftoneLine = 4 + HalftoneRound = 1 + HalftoneSquare = 5 + + +class BitsPerChannelType(IntEnum): + Document16Bits = 16 + Document1Bit = 1 + Document32Bits = 32 + Document8Bits = 8 + + +class BlendMode(IntEnum): + ColorBlend = 22 + ColorBurn = 6 + ColorDodge = 10 + Darken = 4 + DarkerColor = 28 + Difference = 18 + Dissolve = 3 + Divide = 30 + Exclusion = 19 + HardLight = 14 + HardMix = 26 + Hue = 20 + Lighten = 8 + LighterColor = 27 + LinearBurn = 7 + LinearDodge = 11 + LinearLight = 16 + Luminosity = 23 + Multiply = 5 + NormalBlend = 2 + Overlay = 12 + PassThrough = 1 + PinLight = 17 + SaturationBlend = 21 + Screen = 9 + SoftLight = 13 + Subtract = 29 + VividLight = 15 + + +class ByteOrderType(IntEnum): + IBMByteOrder = 1 + MacOSByteOrder = 2 + + +class CameraRAWSettingsType(IntEnum): + CameraDefault = 0 + CustomSettings = 2 + SelectedImage = 1 + + +class CameraRAWSize(IntEnum): + ExtraLargeCameraRAW = 4 + LargeCameraRAW = 3 + MaximumCameraRAW = 5 + MediumCameraRAW = 2 + MinimumCameraRAW = 0 + SmallCameraRAW = 1 + + +class Case(IntEnum): + AllCa = 2 + NormalCase = 1 + SmallCa = 3 + + +class ChangeMode(IntEnum): + ConvertToBitmap = 5 + ConvertToCMYK = 3 + ConvertToGrayscale = 1 + ConvertToIndexedColor = 6 + ConvertToLab = 4 + ConvertToMultiChannel = 7 + ConvertToRGB = 2 + + +class ChannelType(IntEnum): + ComponentChannel = 1 + MaskedAreaAlphaChannel = 2 + SelectedAreaAlphaChannel = 3 + SpotColorChannel = 4 + + +class ColorBlendMode(IntEnum): + ColorBlendMode = 22 + BehindBlend = 24 + ClearBlend = 25 + ColorBurnBlend = 6 + ColorDodgeBlend = 10 + DarkenBlend = 4 + DarkerColorBlend = 28 + DifferenceBlend = 18 + DissolveBlend = 3 + DivideBlend = 30 + ExclusionBlend = 19 + HardLightBlend = 14 + HardMixBlend = 26 + HueBlend = 20 + LightenBlend = 8 + LighterColorBlend = 27 + LinearBurnBlend = 7 + LinearDodgeBlend = 11 + LinearLightBlend = 16 + LuminosityBlend = 23 + MultiplyBlend = 5 + NormalBlendColor = 2 + OverlayBlend = 12 + PinLightBlend = 17 + SaturationBlendColor = 21 + ScreenBlend = 9 + SoftLightBlend = 13 + SubtractBlend = 29 + VividLightBlend = 15 + + +class ColorModel(IntEnum): + CMYKModel = 3 + GrayscaleModel = 1 + HSBModel = 5 + LabModel = 4 + NoModel = 50 + RGBModel = 2 + + +class ColorPicker(IntEnum): + AdobeColorPicker = 1 + AppleColorPicker = 2 + PlugInColorPicker = 4 + WindowsColorPicker = 3 + + +class ColorProfileType(IntEnum): + Custom = 3 + No = 1 + Working = 2 + + +class ColorReductionType(IntEnum): + Adaptive = 2 + BlackWhiteReduction = 5 + CustomReduction = 4 + MacintoshColors = 7 + PerceptualReduction = 0 + Restrictive = 3 + SFWGrayscale = 6 + Selective = 1 + WindowsColors = 8 + + +class ColorSpaceType(IntEnum): + AdobeRGB = 0 + ColorMatchRGB = 1 + ProPhotoRGB = 2 + SRGB = 3 + + +class CopyrightedType(IntEnum): + CopyrightedWork = 1 + PublicDomain = 2 + Unmarked = 3 + + +class CreateFields(IntEnum): + Duplication = 1 + Interpolation = 2 + + +class CropToType(IntEnum): + ArtBox = 5 + BleedBox = 3 + BoundingBox = 0 + CropBox = 2 + MediaBox = 1 + TrimBox = 4 + + +class DCSType(IntEnum): + ColorComposite = 3 + GrayscaleComposite = 2 + NoComposite = 1 + + +class DepthMaource(IntEnum): + ImageHighlight = 4 + LayerMask = 3 + NoSource = 1 + TransparencyChannel = 2 + + +class DescValueType(IntEnum): + AliasType = 11 + BooleanType = 5 + ClassType = 10 + DoubleType = 2 + EnumeratedType = 8 + IntegerType = 1 + LargeIntegerType = 13 + ListType = 6 + ObjectType = 7 + RawType = 12 + ReferenceType = 9 + StringType = 4 + UnitDoubleType = 3 + + +class DialogModes(IntEnum): + DisplayAllDialogs = 1 + DisplayErrorDialogs = 2 + DisplayNoDialogs = 3 + + +class Direction(IntEnum): + Horizontal = 1 + Vertical = 2 + + +class DisplacementMapType(IntEnum): + StretchToFit = 1 + Tile = 2 + + +class DitherType(IntEnum): + Diffusion = 2 + NoDither = 1 + Noise = 4 + Pattern = 3 + + +class DocumentFill(IntEnum): + BackgroundColor = 2 + Transparent = 3 + White = 1 + + +class DocumentMode(IntEnum): + Bitmap = 5 + CMYK = 3 + Duotone = 8 + Grayscale = 1 + IndexedColor = 6 + Lab = 4 + MultiChannel = 7 + RGB = 2 + + +class EditLogItemsType(IntEnum): + Concise = 2 + Detailed = 3 + SessionOnly = 1 + + +class ElementPlacement(IntEnum): + PlaceAfter = 4 + PlaceAtBeginning = 1 + PlaceAtEnd = 2 + PlaceBefore = 3 + PlaceInside = 0 + + +class EliminateFields(IntEnum): + EvenFields = 2 + OddFields = 1 + + +class ExportType(IntEnum): + IllustratorPaths = 1 + SaveForWeb = 2 + + +class ExtensionType(IntEnum): + Lowercase = 2 + Uppercase = 3 + + +class FileNamingType(IntEnum): + Ddmm = 16 + Ddmmyy = 15 + DocumentNameLower = 2 + DocumentNameMixed = 1 + DocumentNameUpper = 3 + ExtensionLower = 17 + ExtensionUpper = 18 + Mmdd = 11 + Mmddyy = 10 + SerialLetterLower = 8 + SerialLetterUpper = 9 + SerialNumber1 = 4 + SerialNumber2 = 5 + SerialNumber3 = 6 + SerialNumber4 = 7 + Yyddmm = 14 + Yymmdd = 13 + Yyyymmdd = 12 + + +class FontPreviewType(IntEnum): + FontPreviewExtraLarge = 4 + FontPreviewHuge = 5 + FontPreviewLarge = 3 + FontPreviewMedium = 2 + FontPreviewNone = 0 + FontPreviewSmall = 1 + + +class ForcedColors(IntEnum): + BlackWhite = 2 + NoForced = 1 + Primaries = 3 + Web = 4 + + +class FormatOptionsType(IntEnum): + OptimizedBaseline = 2 + Progressive = 3 + StandardBaseline = 1 + + +class GalleryConstrainType(IntEnum): + ConstrainBoth = 3 + ConstrainHeight = 2 + ConstrainWidth = 1 + + +class GalleryFontType(IntEnum): + Arial = 1 + CourierNew = 2 + Helvetica = 3 + TimesNewRoman = 4 + + +class GallerySecurityTextColorType(IntEnum): + BlackText = 1 + CustomText = 3 + WhiteText = 2 + + +class GallerySecurityTextPositionType(IntEnum): + Centered = 1 + LowerLeft = 3 + LowerRight = 5 + UpperLeft = 2 + UpperRight = 4 + + +class GallerySecurityTextRotateType(IntEnum): + Clockwise45 = 2 + Clockwise90 = 3 + CounterClockwise45 = 4 + CounterClockwise90 = 5 + Zero = 1 + + +class GallerySecurityType(IntEnum): + Caption = 5 + Copyright = 4 + Credit = 6 + CustomSecurityText = 2 + Filename = 3 + NoSecurity = 1 + Title = 7 + + +class GalleryThumbSizeType(IntEnum): + CustomThumbnail = 4 + Large = 3 + Medium = 2 + Small = 1 + + +class Geometry(IntEnum): + Heptagon = 4 + Hexagon = 2 + Octagon = 5 + Pentagon = 1 + SquareGeometry = 3 + Triangle = 0 + + +class GridLineStyle(IntEnum): + GridDashedLine = 2 + GridDottedLine = 3 + GridSolidLine = 1 + + +class GridSize(IntEnum): + LargeGrid = 4 + MediumGrid = 3 + NoGrid = 1 + SmallGrid = 2 + + +class GuideLineStyle(IntEnum): + GuideDashedLine = 2 + GuideSolidLine = 1 + + +class IllustratorPathType(IntEnum): + AllPaths = 2 + DocumentBounds = 1 + NamedPath = 3 + + +class Intent(IntEnum): + AbsoluteColorimetric = 4 + Perceptual = 1 + RelativeColorimetric = 3 + Saturation = 2 + + +class JavaScriptExecutionMode(IntEnum): + BeforeRunning = 3 + DebuggerOnError = 2 + NeverShowDebugger = 1 + + +class Justification(IntEnum): + Center = 2 + CenterJustified = 5 + FullyJustified = 7 + Left = 1 + LeftJustified = 4 + Right = 3 + RightJustified = 6 + + +class Language(IntEnum): + BrazillianPortuguese = 13 + CanadianFrench = 4 + Danish = 17 + Dutch = 16 + EnglishUK = 2 + EnglishUSA = 1 + Finnish = 5 + French = 3 + German = 6 + Italian = 9 + Norwegian = 10 + NynorskNorwegian = 11 + OldGerman = 7 + Portuguese = 12 + Spanish = 14 + Swedish = 15 + SwissGerman = 8 + + +class LayerCompressionType(IntEnum): + RLELayerCompression = 1 + ZIPLayerCompression = 2 + + +class LayerKind(IntEnum): + BlackAndWhiteLayer = 22 + BrightnessContrastLayer = 9 + ChannelMixerLayer = 12 + ColorBalanceLayer = 8 + ColorLookup = 24 + CurvesLayer = 7 + ExposureLayer = 19 + GradientFillLayer = 4 + GradientMapLayer = 13 + HueSaturationLayer = 10 + InversionLayer = 14 + Layer3D = 20 + LevelsLayer = 6 + NormalLayer = 1 + PatternFillLayer = 5 + PhotoFilterLayer = 18 + PosterizeLayer = 16 + SelectiveColorLayer = 11 + SmartObjectLayer = 17 + SolidFillLayer = 3 + TextLayer = 2 + ThresholdLayer = 15 + Vibrance = 23 + VideoLayer = 21 + + +class LayerType(IntEnum): + ArtLayer = 1 + LayerSet = 2 + + +class MagnificationType(IntEnum): + ActualSize = 0 + FitPage = 1 + + +class MatteType(IntEnum): + BackgroundColorMatte = 3 + BlackMatte = 5 + ForegroundColorMatte = 2 + NetscapeGrayMatte = 7 + NoMatte = 1 + SemiGray = 6 + WhiteMatte = 4 + + +class MeasurementRange(IntEnum): + ActiveMeasurements = 2 + AllMeasurements = 1 + + +class MeasurementSource(IntEnum): + MeasureCountTool = 2 + MeasureRulerTool = 3 + MeasureSelection = 1 + + +class NewDocumentMode(IntEnum): + NewBitmap = 5 + NewCMYK = 3 + NewGray = 1 + NewLab = 4 + NewRGB = 2 + + +class NoiseDistribution(IntEnum): + GaussianNoise = 2 + UniformNoise = 1 + + +class OffsetUndefinedAreas(IntEnum): + OffsetRepeatEdgePixels = 3 + OffsetSetToLayerFill = 1 + OffsetWrapAround = 2 + + +class OpenDocumentMode(IntEnum): + OpenCMYK = 3 + OpenGray = 1 + OpenLab = 4 + OpenRGB = 2 + + +class OpenDocumentType(IntEnum): + AliasPIXOpen = 25 + BMPOpen = 2 + CameraRAWOpen = 32 + CompuServeGIFOpen = 3 + DICOMOpen = 33 + EOpen = 22 + EPICTPreviewOpen = 23 + ETIFFPreviewOpen = 24 + ElectricImageOpen = 26 + FilmstripOpen = 5 + JPEGOpen = 6 + PCXOpen = 7 + PDFOpen = 21 + PICTFileFormatOpen = 10 + PICTResourceFormatOpen = 11 + PNGOpen = 13 + PhotoCDOpen = 9 + PhotoshopDCS_1Open = 18 + PhotoshopDCS_2Open = 19 + PhotoshopEOpen = 4 + PhotoshopOpen = 1 + PhotoshopPDFOpen = 8 + PixarOpen = 12 + PortableBitmapOpen = 27 + RawOpen = 14 + SGIRGBOpen = 29 + ScitexCTOpen = 15 + SoftImageOpen = 30 + TIFFOpen = 17 + TargaOpen = 16 + WavefrontRLAOpen = 28 + WirelessBitmapOpen = 31 + + +class OperatingSystem(IntEnum): + OS2 = 1 + Windows = 2 + + +class Orientation(IntEnum): + Landscape = 1 + Portrait = 2 + + +class OtherPaintingCursors(IntEnum): + PreciseOther = 2 + StandardOther = 1 + + +class PDFCompatibilityType(IntEnum): + PDF13 = 1 + PDF14 = 2 + PDF15 = 3 + PDF16 = 4 + PDF17 = 5 + + +class PDFEncodingType(IntEnum): + PDFJPEG = 2 + PDFJPEG2000HIGH = 9 + PDFJPEG2000LOSSLESS = 14 + PDFJPEG2000LOW = 13 + PDFJPEG2000MED = 11 + PDFJPEG2000MEDHIGH = 10 + PDFJPEG2000MEDLOW = 12 + PDFJPEGHIGH = 4 + PDFJPEGLOW = 8 + PDFJPEGMED = 6 + PDFJPEGMEDHIGH = 5 + PDFJPEGMEDLOW = 7 + PDFNone = 0 + PDFZip = 1 + PDFZip4Bit = 3 + + +class PDFResampleType(IntEnum): + NoResample = 0 + PDFAverage = 1 + PDFBicubic = 3 + PDFSubSample = 2 + + +class PDFStandardType(IntEnum): + NoStandard = 0 + PDFX1A2001 = 1 + PDFX1A2003 = 2 + PDFX32002 = 3 + PDFX32003 = 4 + PDFX42008 = 5 + + +class PICTBitsPerPixel(IntEnum): + PICT16Bits = 16 + PICT2Bits = 2 + PICT32Bits = 32 + PICT4Bits = 4 + PICT8Bits = 8 + + +class PICTCompression(IntEnum): + JPEGHighPICT = 5 + JPEGLowPICT = 2 + JPEGMaximumPICT = 6 + JPEGMediumPICT = 4 + NoPICTCompression = 1 + + +class PaintingCursors(IntEnum): + BrushSize = 3 + Precise = 2 + Standard = 1 + + +class PaletteType(IntEnum): + Exact = 1 + LocalAdaptive = 8 + LocalPerceptual = 6 + LocalSelective = 7 + MacOSPalette = 2 + MasterAdaptive = 11 + MasterPerceptual = 9 + MasterSelective = 10 + PreviousPalette = 12 + Uniform = 5 + WebPalette = 4 + WindowsPalette = 3 + + +class PathKind(IntEnum): + ClippingPath = 2 + NormalPath = 1 + TextMask = 5 + VectorMask = 4 + WorkPath = 3 + + +class PhotoCDColorSpace(IntEnum): + Lab16 = 4 + Lab8 = 3 + RGB16 = 2 + RGB8 = 1 + + +class PhotoCDSize(IntEnum): + ExtraLargePhotoCD = 5 + LargePhotoCD = 4 + MaximumPhotoCD = 6 + MediumPhotoCD = 3 + MinimumPhotoCD = 1 + SmallPhotoCD = 2 + + +class PicturePackageTextType(IntEnum): + CaptionText = 5 + CopyrightText = 4 + CreditText = 6 + FilenameText = 3 + NoText = 1 + OriginText = 7 + UserText = 2 + + +class PointKind(IntEnum): + CornerPoint = 2 + SmoothPoint = 1 + + +class PointType(IntEnum): + PostScriptPoints = 1 + TraditionalPoints = 2 + + +class PolarConversionType(IntEnum): + PolarToRectangular = 2 + RectangularToPolar = 1 + + +class PreviewType(IntEnum): + EightBitTIFF = 3 + MonochromeTIFF = 2 + NoPreview = 1 + + +class PurgeTarget(IntEnum): + AllCaches = 4 + ClipboardCache = 3 + HistoryCaches = 2 + UndoCaches = 1 + + +class QueryStateType(IntEnum): + Always = 1 + Ask = 2 + Never = 3 + + +class RadialBlurMethod(IntEnum): + Spin = 1 + Zoom = 2 + + +class RadialBlurBest(IntEnum): + RadialBlurBest = 3 + RadialBlurDraft = 1 + RadialBlurGood = 2 + + +class RasterizeType(IntEnum): + EntireLayer = 5 + FillContent = 3 + LayerClippingPath = 4 + LinkedLayers = 6 + Shape = 2 + TextContents = 1 + + +class ReferenceFormType(IntEnum): + ReferenceClassType = 7 + ReferenceEnumeratedType = 5 + ReferenceIdentifierType = 3 + ReferenceIndexType = 2 + ReferenceNameType = 1 + ReferenceOffsetType = 4 + ReferencePropertyType = 6 + + +class ResampleMethod(IntEnum): + Automatic = 8 + Bicubic = 4 + BicubicAutomatic = 7 + BicubicSharper = 5 + BicubicSmoother = 6 + Bilinear = 3 + NearestNeighbor = 2 + NoResampling = 1 + PreserveDetails = 9 + + +class ResetTarget(IntEnum): + AllTools = 2 + AllWarnings = 1 + Everything = 3 + + +class RippleSize(IntEnum): + LargeRipple = 3 + MediumRipple = 2 + SmallRipple = 1 + + +class SaveBehavior(IntEnum): + AlwaysSave = 2 + AskWhenSaving = 3 + NeverSave = 1 + + +class SaveDocumentType(IntEnum): + AliasPIXSave = 25 + BMave = 2 + CompuServeGIFSave = 3 + ElectricImageSave = 26 + JPEGSave = 6 + PCXSave = 7 + PICTFileFormatSave = 10 + PICTResourceFormatSave = 11 + PNGSave = 13 + PhotoshopDCS_1Save = 18 + PhotoshopDCS_2Save = 19 + PhotoshopESave = 4 + PhotoshopPDFSave = 8 + Photoshoave = 1 + PixarSave = 12 + PortableBitmaave = 27 + RawSave = 14 + SGIRGBSave = 29 + ScitexCTSave = 15 + SoftImageSave = 30 + TIFFSave = 17 + TargaSave = 16 + WavefrontRLASave = 28 + WirelessBitmaave = 31 + + +class SaveEncoding(IntEnum): + Ascii = 3 + Binary = 1 + JPEGHigh = 5 + JPEGLow = 2 + JPEGMaximum = 6 + JPEGMedium = 4 + + +class SaveLogItemsType(IntEnum): + LogFile = 2 + LogFileAndMetadata = 3 + Metadata = 1 + + +class SaveOptions(IntEnum): + DoNotSaveChanges = 2 + PromptToSaveChanges = 3 + SaveChanges = 1 + + +class SelectionType(IntEnum): + DiminishSelection = 3 + ExtendSelection = 2 + IntersectSelection = 4 + ReplaceSelection = 1 + + +class ShapeOperation(IntEnum): + ShapeAdd = 1 + ShapeIntersect = 3 + ShapeSubtract = 4 + ShapeXOR = 2 + + +class SmartBlurMode(IntEnum): + SmartBlurEdgeOnly = 2 + SmartBlurNormal = 1 + SmartBlurOverlayEdge = 3 + + +class SmartBlurQuality(IntEnum): + SmartBlurHigh = 3 + SmartBlurLow = 1 + SmartBlurMedium = 2 + + +class SourceSpaceType(IntEnum): + DocumentSpace = 1 + ProofSpace = 2 + + +class SpherizeMode(IntEnum): + HorizontalSpherize = 2 + NormalSpherize = 1 + VerticalSpherize = 3 + + +class StrikeThruType(IntEnum): + StrikeBox = 3 + StrikeHeight = 2 + StrikeOff = 1 + + +class StrokeLocation(IntEnum): + CenterStroke = 2 + InsideStroke = 1 + OutsideStroke = 3 + + +class TargaBitsPerPixels(IntEnum): + Targa16Bits = 16 + Targa24Bits = 24 + Targa32Bits = 32 + + +class TextComposer(IntEnum): + AdobeEveryLine = 2 + AdobeSingleLine = 1 + + +class TextType(IntEnum): + ParagraphText = 2 + PointText = 1 + + +class TextureType(IntEnum): + BlocksTexture = 1 + CanvasTexture = 2 + FrostedTexture = 3 + TextureFile = 5 + TinyLensTexture = 4 + + +class TiffEncodingType(IntEnum): + NoTIFFCompression = 1 + TiffJPEG = 3 + TiffLZW = 2 + TiffZIP = 4 + + +class ToolType(IntEnum): + ArtHistoryBrush = 9 + BackgroundEraser = 4 + Blur = 11 + Brush = 2 + Burn = 14 + CloneStamp = 5 + ColorReplacementTool = 16 + Dodge = 13 + Eraser = 3 + HealingBrush = 7 + HistoryBrush = 8 + PatternStamp = 6 + Pencil = 1 + Sharpen = 12 + Smudge = 10 + Sponge = 15 + + +class TransitionType(IntEnum): + BlindsHorizontal = 1 + BlindsVertical = 2 + BoxIn = 4 + BoxOut = 5 + DissolveTransition = 3 + GlitterDown = 6 + GlitterRight = 7 + GlitterRightDown = 8 + NoTrasition = 9 + Random = 10 + SplitHorizontalIn = 11 + SplitHorizontalOut = 12 + SplitVerticalIn = 13 + SplitVerticalOut = 14 + WipeDown = 15 + WipeLeft = 16 + WipeRight = 17 + WipeUp = 18 + + +class TrimType(IntEnum): + BottomRightPixel = 9 + TopLeftPixel = 1 + TransparentPixels = 0 + + +class TypeUnits(IntEnum): + TypeMM = 4 + TypePixels = 1 + TypePoints = 5 + + +class UndefinedAreas(IntEnum): + RepeatEdgePixels = 2 + WrapAround = 1 + + +class UnderlineType(IntEnum): + UnderlineLeft = 3 + UnderlineOff = 1 + UnderlineRight = 2 + + +class Units(IntEnum): + CM = 3 + Inches = 2 + MM = 4 + Percent = 7 + Picas = 6 + Pixels = 1 + Points = 5 + + +class Urgency(IntEnum): + Four = 4 + High = 8 + Low = 1 + UrgencyNone = 0 + Normal = 5 + Seven = 7 + Six = 6 + Three = 3 + Two = 2 + + +class Wartyle(IntEnum): + Arc = 2 + ArcLower = 3 + ArcUpper = 4 + Arch = 5 + Bulge = 6 + Fish = 11 + FishEye = 13 + Flag = 9 + Inflate = 14 + NoWarp = 1 + Rise = 12 + ShellLower = 7 + ShellUpper = 8 + Squeeze = 15 + Twist = 16 + Wave = 10 + + +class WaveType(IntEnum): + Sine = 1 + Square = 3 + Triangular = 2 + + +class WhiteBalanceType(IntEnum): + AsShot = 0 + Auto = 1 + Cloudy = 3 + CustomCameraSettings = 8 + Daylight = 2 + Flash = 7 + Fluorescent = 6 + Shade = 4 + Tungsten = 5 + + +class ZigZagType(IntEnum): + AroundCenter = 1 + OutFromCenter = 2 + PondRipples = 3 + + +__all__ = [ + "AdjustmentReference", + "AnchorPosition", + "AntiAlias", + "AutoKernType", + "BMPDepthType", + "BatchDestinationType", + "BitmapConversionType", + "BitmapHalfToneType", + "BitsPerChannelType", + "BlendMode", + "ByteOrderType", + "CameraRAWSettingsType", + "CameraRAWSize", + "Case", + "ChangeMode", + "ChannelType", + "ColorBlendMode", + "ColorModel", + "ColorPicker", + "ColorProfileType", + "ColorReductionType", + "ColorSpaceType", + "CopyrightedType", + "CreateFields", + "CropToType", + "DCSType", + "DepthMaource", + "DescValueType", + "DialogModes", + "Direction", + "DisplacementMapType", + "DitherType", + "DocumentFill", + "DocumentMode", + "EditLogItemsType", + "ElementPlacement", + "EliminateFields", + "ExportType", + "ExtensionType", + "FileNamingType", + "FontPreviewType", + "ForcedColors", + "FormatOptionsType", + "GalleryConstrainType", + "GalleryFontType", + "GallerySecurityTextColorType", + "GallerySecurityTextPositionType", + "GallerySecurityTextRotateType", + "GallerySecurityType", + "GalleryThumbSizeType", + "Geometry", + "GridLineStyle", + "GridSize", + "GuideLineStyle", + "IllustratorPathType", + "Intent", + "JavaScriptExecutionMode", + "Justification", + "Language", + "LayerCompressionType", + "LayerKind", + "LayerType", + "LensType", + "MagnificationType", + "MatteType", + "MeasurementRange", + "MeasurementSource", + "NewDocumentMode", + "NoiseDistribution", + "OffsetUndefinedAreas", + "OpenDocumentMode", + "OpenDocumentType", + "OperatingSystem", + "Orientation", + "OtherPaintingCursors", + "PDFCompatibilityType", + "PDFEncodingType", + "PDFResampleType", + "PDFStandardType", + "PICTBitsPerPixel", + "PICTCompression", + "PaintingCursors", + "PaletteType", + "PathKind", + "PhotoCDColorSpace", + "PhotoCDSize", + "PicturePackageTextType", + "PointKind", + "PointType", + "PolarConversionType", + "PreviewType", + "PurgeTarget", + "QueryStateType", + "RadialBlurBest", + "RadialBlurMethod", + "RasterizeType", + "ReferenceFormType", + "ResampleMethod", + "ResetTarget", + "RippleSize", + "SaveBehavior", + "SaveDocumentType", + "SaveEncoding", + "SaveLogItemsType", + "SaveOptions", + "SelectionType", + "ShapeOperation", + "SmartBlurMode", + "SmartBlurQuality", + "SourceSpaceType", + "SpherizeMode", + "StrikeThruType", + "StrokeLocation", + "TargaBitsPerPixels", + "TextComposer", + "TextType", + "TextureType", + "TiffEncodingType", + "ToolType", + "TransitionType", + "TrimType", + "TypeUnits", + "UndefinedAreas", + "UnderlineType", + "Units", + "Urgency", + "Wartyle", + "WaveType", + "WhiteBalanceType", + "ZigZagType", +] diff --git a/photoshop/api/errors.py b/photoshop/api/errors.py index de5357a9..1ac9a789 100644 --- a/photoshop/api/errors.py +++ b/photoshop/api/errors.py @@ -1,13 +1,15 @@ -# Import third-party modules -from comtypes import COMError - - -class PhotoshopPythonAPIError(Exception): - pass - - -class PhotoshopPythonAPICOMError(COMError): - pass - - -__all__ = ["PhotoshopPythonAPIError", "PhotoshopPythonAPICOMError"] +# Import third-party modules +from __future__ import annotations + +from comtypes import COMError + + +class PhotoshopPythonAPIError(Exception): + pass + + +class PhotoshopPythonAPICOMError(COMError): + pass + + +__all__ = ["PhotoshopPythonAPICOMError", "PhotoshopPythonAPIError"] diff --git a/photoshop/api/event_id.py b/photoshop/api/event_id.py index 6985b2b4..4d4a14c0 100644 --- a/photoshop/api/event_id.py +++ b/photoshop/api/event_id.py @@ -1,251 +1,253 @@ -# Import built-in modules -from enum import Enum - - -class EventID(str, Enum): - """All event ids.""" - - # Here is a list of JSON CallBack events in Photoshop. - # https://community.adobe.com/t5/get-started/photoshop-json-callback-events-list-up-to-cc2015-ver-16/td-p/4792115?page=1 - TDTransform = "TdT " - Average = "Avrg" - ApplyStyle = "ASty" - Assert = "Asrt" - AccentedEdges = "AccE" - Add = "Add" - AddNoise = "AdNs" - AddTo = "AddT" - Align = "Algn" - All = "All " - AngledStrokes = "AngS" - ApplyImage = "AppI" - BasRelief = "BsRl" - Batch = "Btch" - BatchFromDroplet = "BtcF" - Blur = "Blr " - BlurMore = "BlrM" - Border = "Brdr" - Brightness = "BrgC" - CanvasSize = "CnvS" - ChalkCharcoal = "ChlC" - ChannelMixer = "ChnM" - Charcoal = "Chrc" - Chrome = "Chrm" - Clear = "Cler" - Close = "Cls " - Clouds = "Clds" - ColorBalance = "ClrB" - ColorHalftone = "ClrH" - ColorRange = "ClrR" - ColoredPencil = "ClrP" - ContactSheet = "0B71D221-F8CE-11d2-B21B-0008C75B322C" - ConteCrayon = "CntC" - Contract = "Cntc" - ConvertMode = "CnvM" - Copy = "copy" - CopyEffects = "CpFX" - CopyMerged = "CpyM" - CopyToLayer = "CpTL" - Craquelure = "Crql" - CreateDroplet = "CrtD" - Crop = "Crop" - Crosshatch = "Crsh" - Crystallize = "Crst" - Curves = "Crvs" - Custom = "Cstm" - Cut = "cut " - CutToLayer = "CtTL" - Cutout = "Ct " - DarkStrokes = "DrkS" - DeInterlace = "Dntr" - DefinePattern = "DfnP" - Defringe = "Dfrg" - Delete = "Dlt " - Desaturate = "Dstt" - Deselect = "Dslc" - Despeckle = "Dspc" - DifferenceClouds = "DrfC" - Diffuse = "Dfs " - DiffuseGlow = "DfsG" - DisableLayerFX = "dlfx" - Displace = "Dspl" - Distribute = "Dstr" - Draw = "Draw" - DryBrush = "DryB" - Duplicate = "Dplc" - DustAndScratches = "DstS" - Emboss = "Embs" - Equalize = "Eqlz" - Exchange = "Exch" - Expand = "Expn" - Export = "Expr" - Jumpto = "Jpto" - ExportTransparentImage = "02879e00-cb66-11d1-bc43-0060b0a13dc4" - Extrude = "Extr" - Facet = "Fct " - Fade = "Fade" - Feather = "Fthr" - Fibers = "Fbrs" - Fill = "Fl " - FilmGrain = "FlmG" - Filter = "Fltr" - FindEdges = "FndE" - FitImage = "3caa3434-cb67-11d1-bc43-0060b0a13dc4" - FlattenImage = "FltI" - Flip = "Flip" - Fragment = "Frgm" - Fresco = "Frsc" - GaussianBlur = "GsnB" - Get = "getd" - Glass = "Gls " - GlowingEdges = "GlwE" - Gradient = "Grdn" - GradientMap = "GrMp" - Grain = "Grn " - GraphicPen = "GraP" - Group = "GrpL" - Grow = "Grow" - HalftoneScreen = "HlfS" - Hide = "Hd " - HighPass = "HghP" - HSBHSL = "HsbP" - HueSaturation = "HStr" - ImageSize = "ImgS" - Import = "Impr" - InkOutlines = "InkO" - Intersect = "Intr" - IntersectWith = "IntW" - Inverse = "Invs" - Invert = "Invr" - LensFlare = "LnsF" - Levels = "Lvls" - LightingEffects = "LghE" - Link = "Lnk " - Make = "Mk " - Maximum = "Mxm " - Median = "Mdn " - MergeLayers = "Mrg2" - MergeLayersOld = "MrgL" - MergeSpotChannel = "MSpt" - MergeVisible = "MrgV" - Mezzotint = "Mztn" - Minimum = "Mnm " - ModeChange = "8cba8cd6-cb66-11d1-bc43-0060b0a13dc4" - Mosaic = "Msc " - Mosaic_PLUGIN = "MscT" - MotionBlur = "MtnB" - Move = "move" - NTSCColors = "NTSC" - NeonGlow = "NGlw" - Next = "Nxt " - NotePaper = "NtPr" - Notify = "Ntfy" - Null = "typeNull" - OceanRipple = "OcnR" - Offset = "Ofst" - Open = "Opn " - Paint = "Pnt " - PaintDaubs = "PntD" - PaletteKnife = "PltK" - Paste = "past" - PasteEffects = "PaFX" - PasteInto = "PstI" - PasteOutside = "PstO" - Patchwork = "Ptch" - Photocopy = "Phtc" - PicturePackage = "4C1ABF40-DD82-11d2-B20F-0008C75B322C" - Pinch = "Pnch" - Place = "Plc " - Plaster = "Plst" - PlasticWrap = "PlsW" - Play = "Ply " - Pointillize = "Pntl" - Polar = "Plr " - PosterEdges = "PstE" - Posterize = "Pstr" - Previous = "Prvs" - Print = "Prnt" - ProfileToProfile = "PrfT" - Purge = "Prge" - Quit = "quit" - RadialBlur = "RdlB" - Rasterize = "Rstr" - RasterizeTypeSheet = "RstT" - RemoveBlackMatte = "RmvB" - RemoveLayerMask = "RmvL" - RemoveWhiteMatte = "RmvW" - Rename = "Rnm " - ReplaceColor = "RplC" - Reset = "Rset" - ResizeImage = "1333cf0c-cb67-11d1-bc43-0060b0a13dc4" - Reticulation = "Rtcl" - Revert = "Rvrt" - Ripple = "Rple" - Rotate = "Rtte" - RoughPastels = "RghP" - Save = "save" - Select = "slct" - SelectiveColor = "SlcC" - Set = "setd" - SharpenEdges = "ShrE" - Sharpen = "Shrp" - SharpenMore = "ShrM" - Shear = "Shr " - Show = "Shw " - Similar = "Smlr" - SmartBlur = "SmrB" - Smooth = "Smth" - SmudgeStick = "SmdS" - Solarize = "Slrz" - Spatter = "Spt " - Spherize = "Sphr" - SplitChannels = "SplC" - Sponge = "Spng" - SprayedStrokes = "SprS" - StainedGlass = "StnG" - Stamp = "Stmp" - Stop = "Stop" - Stroke = "Strk" - Subtract = "Sbtr" - SubtractFrom = "SbtF" - Sumie = "Smie" - TakeMergedSnapshot = "TkMr" - TakeSnapshot = "TkSn" - TextureFill = "TxtF" - Texturizer = "Txtz" - Threshold = "Thrs" - Tiles = "Tls " - TornEdges = "TrnE" - TraceContour = "TrcC" - Transform = "Trnf" - Trap = "Trap" - Twirl = "Twrl" - Underpainting = "Undr" - Undo = "undo" - Ungroup = "Ungr" - Unlink = "Unlk" - UnsharpMask = "UnsM" - Variations = "Vrtn" - Wait = "Wait" - WaterPaper = "WtrP" - Watercolor = "Wtrc" - Wave = "Wave" - Wind = "Wnd " - ZigZag = "ZgZg" - BackLight = "BacL" - FillFlash = "FilE" - ColorCast = "ColE" - OpenUntitled = "OpnU" - PresetKind = "presetKindType" - SmartSharpen = "smartSharpen" - PresetKindType = "presetKindType" - PresetKindCustom = "presetKindCustom" - NoiseReduction = "noiseReduction" - BlurType = "blurType" - ContentLayer = "contentLayer" - SaveStage = "saveStage" - SaveStageType = "saveStageType" - SaveSucceeded = "saveSucceeded" - RasterizeLayer = "rasterizeLayer" - ForceNotify = "forceNotify" - PlacedLayerEditContents = "placedLayerEditContents" +# Import built-in modules +from __future__ import annotations + +from enum import Enum + + +class EventID(str, Enum): + """All event ids.""" + + # Here is a list of JSON CallBack events in Photoshop. + # https://community.adobe.com/t5/get-started/photoshop-json-callback-events-list-up-to-cc2015-ver-16/td-p/4792115?page=1 + TDTransform = "TdT " + Average = "Avrg" + ApplyStyle = "ASty" + Assert = "Asrt" + AccentedEdges = "AccE" + Add = "Add" + AddNoise = "AdNs" + AddTo = "AddT" + Align = "Algn" + All = "All " + AngledStrokes = "AngS" + ApplyImage = "AppI" + BasRelief = "BsRl" + Batch = "Btch" + BatchFromDroplet = "BtcF" + Blur = "Blr " + BlurMore = "BlrM" + Border = "Brdr" + Brightness = "BrgC" + CanvasSize = "CnvS" + ChalkCharcoal = "ChlC" + ChannelMixer = "ChnM" + Charcoal = "Chrc" + Chrome = "Chrm" + Clear = "Cler" + Close = "Cls " + Clouds = "Clds" + ColorBalance = "ClrB" + ColorHalftone = "ClrH" + ColorRange = "ClrR" + ColoredPencil = "ClrP" + ContactSheet = "0B71D221-F8CE-11d2-B21B-0008C75B322C" + ConteCrayon = "CntC" + Contract = "Cntc" + ConvertMode = "CnvM" + Copy = "copy" + CopyEffects = "CpFX" + CopyMerged = "CpyM" + CopyToLayer = "CpTL" + Craquelure = "Crql" + CreateDroplet = "CrtD" + Crop = "Crop" + Crosshatch = "Crsh" + Crystallize = "Crst" + Curves = "Crvs" + Custom = "Cstm" + Cut = "cut " + CutToLayer = "CtTL" + Cutout = "Ct " + DarkStrokes = "DrkS" + DeInterlace = "Dntr" + DefinePattern = "DfnP" + Defringe = "Dfrg" + Delete = "Dlt " + Desaturate = "Dstt" + Deselect = "Dslc" + Despeckle = "Dspc" + DifferenceClouds = "DrfC" + Diffuse = "Dfs " + DiffuseGlow = "DfsG" + DisableLayerFX = "dlfx" + Displace = "Dspl" + Distribute = "Dstr" + Draw = "Draw" + DryBrush = "DryB" + Duplicate = "Dplc" + DustAndScratches = "DstS" + Emboss = "Embs" + Equalize = "Eqlz" + Exchange = "Exch" + Expand = "Expn" + Export = "Expr" + Jumpto = "Jpto" + ExportTransparentImage = "02879e00-cb66-11d1-bc43-0060b0a13dc4" + Extrude = "Extr" + Facet = "Fct " + Fade = "Fade" + Feather = "Fthr" + Fibers = "Fbrs" + Fill = "Fl " + FilmGrain = "FlmG" + Filter = "Fltr" + FindEdges = "FndE" + FitImage = "3caa3434-cb67-11d1-bc43-0060b0a13dc4" + FlattenImage = "FltI" + Flip = "Flip" + Fragment = "Frgm" + Fresco = "Frsc" + GaussianBlur = "GsnB" + Get = "getd" + Glass = "Gls " + GlowingEdges = "GlwE" + Gradient = "Grdn" + GradientMap = "GrMp" + Grain = "Grn " + GraphicPen = "GraP" + Group = "GrpL" + Grow = "Grow" + HalftoneScreen = "HlfS" + Hide = "Hd " + HighPass = "HghP" + HSBHSL = "HsbP" + HueSaturation = "HStr" + ImageSize = "ImgS" + Import = "Impr" + InkOutlines = "InkO" + Intersect = "Intr" + IntersectWith = "IntW" + Inverse = "Invs" + Invert = "Invr" + LensFlare = "LnsF" + Levels = "Lvls" + LightingEffects = "LghE" + Link = "Lnk " + Make = "Mk " + Maximum = "Mxm " + Median = "Mdn " + MergeLayers = "Mrg2" + MergeLayersOld = "MrgL" + MergeSpotChannel = "MSpt" + MergeVisible = "MrgV" + Mezzotint = "Mztn" + Minimum = "Mnm " + ModeChange = "8cba8cd6-cb66-11d1-bc43-0060b0a13dc4" + Mosaic = "Msc " + Mosaic_PLUGIN = "MscT" + MotionBlur = "MtnB" + Move = "move" + NTSCColors = "NTSC" + NeonGlow = "NGlw" + Next = "Nxt " + NotePaper = "NtPr" + Notify = "Ntfy" + Null = "typeNull" + OceanRipple = "OcnR" + Offset = "Ofst" + Open = "Opn " + Paint = "Pnt " + PaintDaubs = "PntD" + PaletteKnife = "PltK" + Paste = "past" + PasteEffects = "PaFX" + PasteInto = "PstI" + PasteOutside = "PstO" + Patchwork = "Ptch" + Photocopy = "Phtc" + PicturePackage = "4C1ABF40-DD82-11d2-B20F-0008C75B322C" + Pinch = "Pnch" + Place = "Plc " + Plaster = "Plst" + PlasticWrap = "PlsW" + Play = "Ply " + Pointillize = "Pntl" + Polar = "Plr " + PosterEdges = "PstE" + Posterize = "Pstr" + Previous = "Prvs" + Print = "Prnt" + ProfileToProfile = "PrfT" + Purge = "Prge" + Quit = "quit" + RadialBlur = "RdlB" + Rasterize = "Rstr" + RasterizeTypeSheet = "RstT" + RemoveBlackMatte = "RmvB" + RemoveLayerMask = "RmvL" + RemoveWhiteMatte = "RmvW" + Rename = "Rnm " + ReplaceColor = "RplC" + Reset = "Rset" + ResizeImage = "1333cf0c-cb67-11d1-bc43-0060b0a13dc4" + Reticulation = "Rtcl" + Revert = "Rvrt" + Ripple = "Rple" + Rotate = "Rtte" + RoughPastels = "RghP" + Save = "save" + Select = "slct" + SelectiveColor = "SlcC" + Set = "setd" + SharpenEdges = "ShrE" + Sharpen = "Shrp" + SharpenMore = "ShrM" + Shear = "Shr " + Show = "Shw " + Similar = "Smlr" + SmartBlur = "SmrB" + Smooth = "Smth" + SmudgeStick = "SmdS" + Solarize = "Slrz" + Spatter = "Spt " + Spherize = "Sphr" + SplitChannels = "SplC" + Sponge = "Spng" + SprayedStrokes = "SprS" + StainedGlass = "StnG" + Stamp = "Stmp" + Stop = "Stop" + Stroke = "Strk" + Subtract = "Sbtr" + SubtractFrom = "SbtF" + Sumie = "Smie" + TakeMergedSnapshot = "TkMr" + TakeSnapshot = "TkSn" + TextureFill = "TxtF" + Texturizer = "Txtz" + Threshold = "Thrs" + Tiles = "Tls " + TornEdges = "TrnE" + TraceContour = "TrcC" + Transform = "Trnf" + Trap = "Trap" + Twirl = "Twrl" + Underpainting = "Undr" + Undo = "undo" + Ungroup = "Ungr" + Unlink = "Unlk" + UnsharpMask = "UnsM" + Variations = "Vrtn" + Wait = "Wait" + WaterPaper = "WtrP" + Watercolor = "Wtrc" + Wave = "Wave" + Wind = "Wnd " + ZigZag = "ZgZg" + BackLight = "BacL" + FillFlash = "FilE" + ColorCast = "ColE" + OpenUntitled = "OpnU" + PresetKind = "presetKindType" + SmartSharpen = "smartSharpen" + PresetKindType = "presetKindType" + PresetKindCustom = "presetKindCustom" + NoiseReduction = "noiseReduction" + BlurType = "blurType" + ContentLayer = "contentLayer" + SaveStage = "saveStage" + SaveStageType = "saveStageType" + SaveSucceeded = "saveSucceeded" + RasterizeLayer = "rasterizeLayer" + ForceNotify = "forceNotify" + PlacedLayerEditContents = "placedLayerEditContents" diff --git a/photoshop/api/open_options/__init__.py b/photoshop/api/open_options/__init__.py index 720c0c9e..086f0407 100644 --- a/photoshop/api/open_options/__init__.py +++ b/photoshop/api/open_options/__init__.py @@ -1,6 +1,7 @@ -from ..open_options.eps import EPSOpenOptions +from __future__ import annotations +from ..open_options.eps import EPSOpenOptions __all__ = [ - EPSOpenOptions.__name__, + "EPSOpenOptions", ] diff --git a/photoshop/api/open_options/eps.py b/photoshop/api/open_options/eps.py index fee488c9..8827f4b4 100644 --- a/photoshop/api/open_options/eps.py +++ b/photoshop/api/open_options/eps.py @@ -1,47 +1,49 @@ -# Import local modules -from photoshop.api._core import Photoshop - - -class EPSOpenOptions(Photoshop): - """Options for saving a document in EPS format. - - using the `Document.saveAs()` - - """ - - object_name = "EPSOpenOptions" - - def __init__(self): - super().__init__() - - @property - def antiAlias(self): - return self.app.antiAlias - - @property - def constrainProportions(self): - return self.app.constrainProportions - - @property - def height(self): - return self.app.height - - @property - def mode(self): - return self.app.mode - - @property - def resolution(self): - return self.app.resolution - - @property - def width(self): - return self.app.width - - @property - def embedColorProfile(self): - return self.app.embedColorProfile - - @embedColorProfile.setter - def embedColorProfile(self, boolean): - self.app.embedColorProfile = boolean +# Import local modules +from __future__ import annotations + +from photoshop.api._core import Photoshop + + +class EPSOpenOptions(Photoshop): + """Options for saving a document in EPS format. + + using the `Document.saveAs()` + + """ + + object_name = "EPSOpenOptions" + + def __init__(self): + super().__init__() + + @property + def antiAlias(self): + return self.app.antiAlias + + @property + def constrainProportions(self): + return self.app.constrainProportions + + @property + def height(self): + return self.app.height + + @property + def mode(self): + return self.app.mode + + @property + def resolution(self): + return self.app.resolution + + @property + def width(self): + return self.app.width + + @property + def embedColorProfile(self): + return self.app.embedColorProfile + + @embedColorProfile.setter + def embedColorProfile(self, boolean): + self.app.embedColorProfile = boolean diff --git a/photoshop/api/save_options/__init__.py b/photoshop/api/save_options/__init__.py index 7c01d781..d13da65d 100644 --- a/photoshop/api/save_options/__init__.py +++ b/photoshop/api/save_options/__init__.py @@ -1,25 +1,25 @@ # Import local modules +from __future__ import annotations + 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 ExportOptionsSaveForWeb -from photoshop.api.save_options.png import PNGSaveOptions +from photoshop.api.save_options.png import ExportOptionsSaveForWeb, 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 - __all__ = [ - BMPSaveOptions.__name__, - EPSSaveOptions.__name__, - GIFSaveOptions.__name__, - JPEGSaveOptions.__name__, - PDFSaveOptions.__name__, - ExportOptionsSaveForWeb.__name__, - PNGSaveOptions.__name__, - PhotoshopSaveOptions.__name__, - TargaSaveOptions.__name__, - TiffSaveOptions.__name__, + "BMPSaveOptions", + "EPSSaveOptions", + "ExportOptionsSaveForWeb", + "GIFSaveOptions", + "JPEGSaveOptions", + "PDFSaveOptions", + "PNGSaveOptions", + "PhotoshopSaveOptions", + "TargaSaveOptions", + "TiffSaveOptions", ] diff --git a/photoshop/api/save_options/bmp.py b/photoshop/api/save_options/bmp.py index 8a1c1ae8..0a63ba31 100644 --- a/photoshop/api/save_options/bmp.py +++ b/photoshop/api/save_options/bmp.py @@ -1,27 +1,29 @@ -"""Options for saving a document in BMO format.""" - -# Import local modules -from photoshop.api._core import Photoshop - - -class BMPSaveOptions(Photoshop): - """Options for saving a document in BMP format.""" - - object_name = "BMPSaveOptions" - - def __init__(self): - super().__init__() - - @property - def alphaChannels(self): - """State to save the alpha channels.""" - return self.app.alphaChannels - - @alphaChannels.setter - def alphaChannels(self, value): - """Sets whether to save the alpha channels or not. - - Args: - - """ - self.app.alphaChannels = value +"""Options for saving a document in BMO format.""" + +# Import local modules +from __future__ import annotations + +from photoshop.api._core import Photoshop + + +class BMPSaveOptions(Photoshop): + """Options for saving a document in BMP format.""" + + object_name = "BMPSaveOptions" + + def __init__(self): + super().__init__() + + @property + def alphaChannels(self): + """State to save the alpha channels.""" + return self.app.alphaChannels + + @alphaChannels.setter + def alphaChannels(self, value): + """Sets whether to save the alpha channels or not. + + Args: + + """ + self.app.alphaChannels = value diff --git a/photoshop/api/save_options/eps.py b/photoshop/api/save_options/eps.py index d44aefb6..3dfd0402 100644 --- a/photoshop/api/save_options/eps.py +++ b/photoshop/api/save_options/eps.py @@ -1,97 +1,99 @@ -# Import local modules -from photoshop.api._core import Photoshop - - -class EPSSaveOptions(Photoshop): - """Options for saving a document in EPS format. - - using the `Document.saveAs()` - - """ - - object_name = "EPSSaveOptions" - - def __init__(self): - super().__init__() - - @property - def embedColorProfile(self) -> bool: - """True to embed the color profile in this document.""" - return self.app.embedColorProfile - - @embedColorProfile.setter - def embedColorProfile(self, boolean: bool): - """True to embed the color profile in this document.""" - self.app.embedColorProfile = boolean - - @property - def encoding(self): - return self.app.encoding - - @encoding.setter - def encoding(self, value: bool): - self.app.encoding = value - - @property - def halftoneScreen(self): - return self.app.halftoneScreen - - @halftoneScreen.setter - def halftoneScreen(self, value: bool): - self.app.halftoneScreen = value - - @property - def interpolation(self): - return self.app.interpolation - - @interpolation.setter - def interpolation(self, value: bool): - self.app.interpolation = value - - @property - def preview(self): - return self.app.preview - - @preview.setter - def preview(self, value: bool): - self.app.preview = value - - @property - def psColorManagement(self): - return self.app.psColorManagement - - @psColorManagement.setter - def psColorManagement(self, value: bool): - self.app.psColorManagement = value - - @property - def transferFunction(self): - return self.app.transferFunction - - @transferFunction.setter - def transferFunction(self, value: bool): - self.app.transferFunction = value - - @property - def transparentWhites(self) -> bool: - """True to display white areas as transparent""" - return self.app.transparentWhites - - @transparentWhites.setter - def transparentWhites(self, value: bool): - """True to display white areas as transparent""" - self.app.transparentWhites = value - - @property - def vectorData(self): - """True to include vector data.""" - return self.app.vectorData - - @vectorData.setter - def vectorData(self, value: bool): - """True to include vector data. - - Valid only if the document includes vector data (text). - - """ - self.app.vectorData = value +# Import local modules +from __future__ import annotations + +from photoshop.api._core import Photoshop + + +class EPSSaveOptions(Photoshop): + """Options for saving a document in EPS format. + + using the `Document.saveAs()` + + """ + + object_name = "EPSSaveOptions" + + def __init__(self): + super().__init__() + + @property + def embedColorProfile(self) -> bool: + """True to embed the color profile in this document.""" + return self.app.embedColorProfile + + @embedColorProfile.setter + def embedColorProfile(self, boolean: bool): + """True to embed the color profile in this document.""" + self.app.embedColorProfile = boolean + + @property + def encoding(self): + return self.app.encoding + + @encoding.setter + def encoding(self, value: bool): + self.app.encoding = value + + @property + def halftoneScreen(self): + return self.app.halftoneScreen + + @halftoneScreen.setter + def halftoneScreen(self, value: bool): + self.app.halftoneScreen = value + + @property + def interpolation(self): + return self.app.interpolation + + @interpolation.setter + def interpolation(self, value: bool): + self.app.interpolation = value + + @property + def preview(self): + return self.app.preview + + @preview.setter + def preview(self, value: bool): + self.app.preview = value + + @property + def psColorManagement(self): + return self.app.psColorManagement + + @psColorManagement.setter + def psColorManagement(self, value: bool): + self.app.psColorManagement = value + + @property + def transferFunction(self): + return self.app.transferFunction + + @transferFunction.setter + def transferFunction(self, value: bool): + self.app.transferFunction = value + + @property + def transparentWhites(self) -> bool: + """True to display white areas as transparent""" + return self.app.transparentWhites + + @transparentWhites.setter + def transparentWhites(self, value: bool): + """True to display white areas as transparent""" + self.app.transparentWhites = value + + @property + def vectorData(self): + """True to include vector data.""" + return self.app.vectorData + + @vectorData.setter + def vectorData(self, value: bool): + """True to include vector data. + + Valid only if the document includes vector data (text). + + """ + self.app.vectorData = value diff --git a/photoshop/api/save_options/gif.py b/photoshop/api/save_options/gif.py index df667305..772155fa 100644 --- a/photoshop/api/save_options/gif.py +++ b/photoshop/api/save_options/gif.py @@ -1,83 +1,85 @@ -# Import local modules -from photoshop.api._core import Photoshop - - -class GIFSaveOptions(Photoshop): - """Options for saving a document in GIF format.""" - - object_name = "GIFSaveOptions" - - def __init__(self): - super().__init__() - - @property - def colors(self): - return self.app.color - - @colors.setter - def colors(self, value): - self.app.colors = value - - @property - def dither(self): - return self.app.dither - - @dither.setter - def dither(self, value): - self.app.dither = value - - @property - def ditherAmount(self): - return self.app.ditherAmount - - @ditherAmount.setter - def ditherAmount(self, value): - self.app.ditherAmount = value - - @property - def forced(self): - return self.app.forced - - @forced.setter - def forced(self, value): - self.app.forced = value - - @property - def interlaced(self): - return self.app.interlaced - - @interlaced.setter - def interlaced(self, value): - self.app.interlaced = value - - @property - def matte(self): - return self.app.matte - - @matte.setter - def matte(self, value): - self.app.matte = value - - @property - def palette(self): - return self.app.palette - - @palette.setter - def palette(self, value): - self.app.palette = value - - @property - def preserveExactColors(self): - return self.app.preserveExactColors - - @preserveExactColors.setter - def preserveExactColors(self, value): - self.app.preserveExactColors = value - - @property - def transparency(self): - return self.app.transparency - - @transparency.setter - def transparency(self, value): - self.app.transparency = value +# Import local modules +from __future__ import annotations + +from photoshop.api._core import Photoshop + + +class GIFSaveOptions(Photoshop): + """Options for saving a document in GIF format.""" + + object_name = "GIFSaveOptions" + + def __init__(self): + super().__init__() + + @property + def colors(self): + return self.app.color + + @colors.setter + def colors(self, value): + self.app.colors = value + + @property + def dither(self): + return self.app.dither + + @dither.setter + def dither(self, value): + self.app.dither = value + + @property + def ditherAmount(self): + return self.app.ditherAmount + + @ditherAmount.setter + def ditherAmount(self, value): + self.app.ditherAmount = value + + @property + def forced(self): + return self.app.forced + + @forced.setter + def forced(self, value): + self.app.forced = value + + @property + def interlaced(self): + return self.app.interlaced + + @interlaced.setter + def interlaced(self, value): + self.app.interlaced = value + + @property + def matte(self): + return self.app.matte + + @matte.setter + def matte(self, value): + self.app.matte = value + + @property + def palette(self): + return self.app.palette + + @palette.setter + def palette(self, value): + self.app.palette = value + + @property + def preserveExactColors(self): + return self.app.preserveExactColors + + @preserveExactColors.setter + def preserveExactColors(self, value): + self.app.preserveExactColors = value + + @property + def transparency(self): + return self.app.transparency + + @transparency.setter + def transparency(self, value): + self.app.transparency = value diff --git a/photoshop/api/save_options/jpg.py b/photoshop/api/save_options/jpg.py index b54a3353..24e6f0de 100644 --- a/photoshop/api/save_options/jpg.py +++ b/photoshop/api/save_options/jpg.py @@ -1,58 +1,61 @@ -# Import local modules -from photoshop.api._core import Photoshop -from photoshop.api.enumerations import MatteType - - -class JPEGSaveOptions(Photoshop): - """Options for saving a document in JPEG format.""" - - object_name = "JPEGSaveOptions" - - def __init__(self, quality=5, embedColorProfile=True, matte=MatteType.NoMatte): - super().__init__() - self.quality = quality - self.embedColorProfile = embedColorProfile - self.matte = matte - - @property - def quality(self): - return self.app.quality - - @quality.setter - def quality(self, value): - self.app.quality = value - - @property - def formatOptions(self): - """The download format to use.""" - return self.app.formatOptions - - @formatOptions.setter - def formatOptions(self, value): - self.app.formatOptions = value - - @property - def embedColorProfile(self): - return self.app.embedColorProfile - - @embedColorProfile.setter - def embedColorProfile(self, value): - self.app.embedColorProfile = value - - @property - def matte(self): - """The color to use to fill anti-aliased edges adjacent to - transparent""" - return self.app.matte - - @matte.setter - def matte(self, value): - self.app.matte = value - - @property - def scans(self): - return self.app.scans - - @scans.setter - def scans(self, value): - self.app.scans = value +# Import local modules +from __future__ import annotations + +from photoshop.api._core import Photoshop +from photoshop.api.enumerations import MatteType + + +class JPEGSaveOptions(Photoshop): + """Options for saving a document in JPEG format.""" + + object_name = "JPEGSaveOptions" + + def __init__(self, quality=5, embedColorProfile=True, matte=MatteType.NoMatte): + super().__init__() + self.quality = quality + self.embedColorProfile = embedColorProfile + self.matte = matte + + @property + def quality(self): + return self.app.quality + + @quality.setter + def quality(self, value): + self.app.quality = value + + @property + def formatOptions(self): + """The download format to use.""" + return self.app.formatOptions + + @formatOptions.setter + def formatOptions(self, value): + self.app.formatOptions = value + + @property + def embedColorProfile(self): + return self.app.embedColorProfile + + @embedColorProfile.setter + def embedColorProfile(self, value): + self.app.embedColorProfile = value + + @property + def matte(self): + """The color to use to fill anti-aliased edges adjacent to + transparent + """ + return self.app.matte + + @matte.setter + def matte(self, value): + self.app.matte = value + + @property + def scans(self): + return self.app.scans + + @scans.setter + def scans(self, value): + self.app.scans = value diff --git a/photoshop/api/save_options/pdf.py b/photoshop/api/save_options/pdf.py index fdc4fb18..57652c59 100644 --- a/photoshop/api/save_options/pdf.py +++ b/photoshop/api/save_options/pdf.py @@ -1,356 +1,369 @@ -"""Options for saving a document in Adobe PDF format. - -using the Document.saveAs() method. - -""" - -# Import local modules -from photoshop.api._core import Photoshop -from photoshop.api.enumerations import PDFEncodingType -from photoshop.api.enumerations import PDFResampleType -from photoshop.api.errors import COMError - - -# pylint: disable=too-many-instance-attributes,too-many-public-methods -class PDFSaveOptions(Photoshop): - """Options for saving a document in PDF format.""" - - object_name = "PDFSaveOptions" - - def __init__(self, **kwargs): - 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) - - @property - def alphaChannels(self): - """True to save the alpha channels with the file.""" - return self.app.alphaChannels - - @alphaChannels.setter - def alphaChannels(self, value): - """True to save the alpha channels with the file.""" - self.app.alphaChannels = value - - @property - def annotations(self): - """If true, the annotations are saved.""" - return self.app.anotations - - @annotations.setter - def annotations(self, value): - """If true, the annotations are saved.""" - self.app.annotations = value - - @property - def colorConversion(self): - """If true, converts the color profile to the destination profile.""" - return self.app.colorConversion - - @colorConversion.setter - def colorConversion(self, value): - """If true, converts the color profile to the destination profile.""" - self.app.colorConversion = value - - @property - def convertToEightBit(self): - """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): - """If true, converts a 16-bit image to 8-bit for better - compatibility with other applications.""" - self.app.convertToEightBit = value - - @property - def description(self): - """Description of the save options in use.""" - return self.app.description - - @description.setter - def description(self, text): - """Description of the save options in use.""" - self.app.description = text - - @property - def destinationProfile(self): - """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." - ) - - @destinationProfile.setter - def destinationProfile(self, value): - """Describes the final RGB or CMYK output device, - such as a monitor or press standard.""" - self.app.destinationProfile = value - - @property - def downSample(self): - """The downsample method to use.""" - return self.app.downSample - - @downSample.setter - def downSample(self, value): - """The downsample method to use.""" - self.app.downSample = value - - @property - def downSampleSize(self): - """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." - ) - - @downSampleSize.setter - def downSampleSize(self, value): - """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): - """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." - ) - - @downSampleSizeLimit.setter - def downSampleSizeLimit(self, value: float): - """Limits downsampling or subsampling to images that exceed this - value (in pixels per inch).""" - self.app.downSampleSizeLimit = value - - @property - def embedColorProfile(self): - """If true, the color profile is embedded in the document.""" - return self.app.embedColorProfile - - @embedColorProfile.setter - def embedColorProfile(self, value: bool): - """If true, the color profile is embedded in the document.""" - self.app.embedColorProfile = value - - @property - def embedThumbnail(self): - """If true, includes a small preview image in Acrobat.""" - return self.app.embedThumbnail - - @embedThumbnail.setter - def embedThumbnail(self, value: bool): - """If true, includes a small preview image in Acrobat.""" - self.app.embedThumbnail = value - - @property - def encoding(self): - """The encoding method to use.""" - try: - return self.app.encoding - except COMError: - return self.encoding_types.PDFJPEG - - @encoding.setter - def encoding(self, value: str): - """The encoding method to use.""" - self.app.encoding = value - - @property - def jpegQuality(self): - """Get the quality of the produced image.""" - return self.app.jpegQuality - - @jpegQuality.setter - def jpegQuality(self, quality: int): - """Set the quality of the produced image. - - Valid only for JPEG-encoded PDF documents. Range: 0 to 12. - - """ - self.app.jpegQuality = quality - - @property - def layers(self): - """If true, the layers are saved.""" - return self.app.layers - - @layers.setter - def layers(self, value: bool): - """If true, the layers are saved.""" - self.app.layers = value - - @property - def optimizeForWeb(self): - """If true, improves performance of PDFs on Web servers.""" - return self.app.optimizeForWeb - - @optimizeForWeb.setter - def optimizeForWeb(self, value: bool): - """If true, improves performance of PDFs on Web servers.""" - self.app.optimizeForWeb = value - - @property - def outputCondition(self): - """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): - """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): - """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." - ) - - @outputConditionID.setter - def outputConditionID(self, value): - """The identifier for the output condition.""" - self.app.outputConditionID = value - - @property - def preserveEditing(self): - """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." - ) - - @preserveEditing.setter - def preserveEditing(self, value): - """If true, allows users to reopen the PDF in Photoshop with - native Photoshop data intact.""" - self.app.preserveEditing = value - - @property - def presetFile(self): - """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." - ) - - @presetFile.setter - def presetFile(self, file_name): - """The preset file to use for settings; overrides other settings.""" - self.app.presetFile = file_name - - @property - def profileInclusionPolicy(self): - """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." - ) - - @profileInclusionPolicy.setter - def profileInclusionPolicy(self, value): - """If true, shows which profiles to include.""" - self.app.profileInclusionPolicy = value - - @property - def registryName(self): - """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." - ) - - @registryName.setter - def registryName(self, value): - """The URL where the output condition is registered.""" - self.app.registryName = value - - @property - def spotColors(self): - """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." - ) - - @spotColors.setter - def spotColors(self, value): - """If true, the spot colors are saved.""" - self.app.spotColors = value - - @property - def tileSize(self): - """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." - ) - - @tileSize.setter - def tileSize(self, value): - """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, - ): - raise ValueError("tileSize only work in JPEG2000. Please " "change PDFSaveOptions.encoding to JPEG2000.") - self.app.tileSize = value - - @property - def view(self): - """If true, opens the saved PDF in Acrobat.""" - return self.app.view - - @view.setter - def view(self, value): - """If true, opens the saved PDF in Acrobat.""" - self.app.view = value +"""Options for saving a document in Adobe PDF format. + +using the Document.saveAs() method. + +""" + +# Import local modules +from __future__ import annotations + +from photoshop.api._core import Photoshop +from photoshop.api.enumerations import PDFEncodingType, PDFResampleType +from photoshop.api.errors import COMError + + +# pylint: disable=too-many-instance-attributes,too-many-public-methods +class PDFSaveOptions(Photoshop): + """Options for saving a document in PDF format.""" + + object_name = "PDFSaveOptions" + + def __init__(self, **kwargs): + 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) + + @property + def alphaChannels(self): + """True to save the alpha channels with the file.""" + return self.app.alphaChannels + + @alphaChannels.setter + def alphaChannels(self, value): + """True to save the alpha channels with the file.""" + self.app.alphaChannels = value + + @property + def annotations(self): + """If true, the annotations are saved.""" + return self.app.anotations + + @annotations.setter + def annotations(self, value): + """If true, the annotations are saved.""" + self.app.annotations = value + + @property + def colorConversion(self): + """If true, converts the color profile to the destination profile.""" + return self.app.colorConversion + + @colorConversion.setter + def colorConversion(self, value): + """If true, converts the color profile to the destination profile.""" + self.app.colorConversion = value + + @property + def convertToEightBit(self): + """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): + """If true, converts a 16-bit image to 8-bit for better + compatibility with other applications. + """ + self.app.convertToEightBit = value + + @property + def description(self): + """Description of the save options in use.""" + return self.app.description + + @description.setter + def description(self, text): + """Description of the save options in use.""" + self.app.description = text + + @property + def destinationProfile(self): + """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.", + ) + + @destinationProfile.setter + def destinationProfile(self, value): + """Describes the final RGB or CMYK output device, + such as a monitor or press standard. + """ + self.app.destinationProfile = value + + @property + def downSample(self): + """The downsample method to use.""" + return self.app.downSample + + @downSample.setter + def downSample(self, value): + """The downsample method to use.""" + self.app.downSample = value + + @property + def downSampleSize(self): + """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.", + ) + + @downSampleSize.setter + def downSampleSize(self, value): + """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): + """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.", + ) + + @downSampleSizeLimit.setter + def downSampleSizeLimit(self, value: float): + """Limits downsampling or subsampling to images that exceed this + value (in pixels per inch). + """ + self.app.downSampleSizeLimit = value + + @property + def embedColorProfile(self): + """If true, the color profile is embedded in the document.""" + return self.app.embedColorProfile + + @embedColorProfile.setter + def embedColorProfile(self, value: bool): + """If true, the color profile is embedded in the document.""" + self.app.embedColorProfile = value + + @property + def embedThumbnail(self): + """If true, includes a small preview image in Acrobat.""" + return self.app.embedThumbnail + + @embedThumbnail.setter + def embedThumbnail(self, value: bool): + """If true, includes a small preview image in Acrobat.""" + self.app.embedThumbnail = value + + @property + def encoding(self): + """The encoding method to use.""" + try: + return self.app.encoding + except COMError: + return self.encoding_types.PDFJPEG + + @encoding.setter + def encoding(self, value: str): + """The encoding method to use.""" + self.app.encoding = value + + @property + def jpegQuality(self): + """Get the quality of the produced image.""" + return self.app.jpegQuality + + @jpegQuality.setter + def jpegQuality(self, quality: int): + """Set the quality of the produced image. + + Valid only for JPEG-encoded PDF documents. Range: 0 to 12. + + """ + self.app.jpegQuality = quality + + @property + def layers(self): + """If true, the layers are saved.""" + return self.app.layers + + @layers.setter + def layers(self, value: bool): + """If true, the layers are saved.""" + self.app.layers = value + + @property + def optimizeForWeb(self): + """If true, improves performance of PDFs on Web servers.""" + return self.app.optimizeForWeb + + @optimizeForWeb.setter + def optimizeForWeb(self, value: bool): + """If true, improves performance of PDFs on Web servers.""" + self.app.optimizeForWeb = value + + @property + def outputCondition(self): + """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): + """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): + """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.", + ) + + @outputConditionID.setter + def outputConditionID(self, value): + """The identifier for the output condition.""" + self.app.outputConditionID = value + + @property + def preserveEditing(self): + """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.", + ) + + @preserveEditing.setter + def preserveEditing(self, value): + """If true, allows users to reopen the PDF in Photoshop with + native Photoshop data intact. + """ + self.app.preserveEditing = value + + @property + def presetFile(self): + """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.", + ) + + @presetFile.setter + def presetFile(self, file_name): + """The preset file to use for settings; overrides other settings.""" + self.app.presetFile = file_name + + @property + def profileInclusionPolicy(self): + """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.", + ) + + @profileInclusionPolicy.setter + def profileInclusionPolicy(self, value): + """If true, shows which profiles to include.""" + self.app.profileInclusionPolicy = value + + @property + def registryName(self): + """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.", + ) + + @registryName.setter + def registryName(self, value): + """The URL where the output condition is registered.""" + self.app.registryName = value + + @property + def spotColors(self): + """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.", + ) + + @spotColors.setter + def spotColors(self, value): + """If true, the spot colors are saved.""" + self.app.spotColors = value + + @property + def tileSize(self): + """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.", + ) + + @tileSize.setter + def tileSize(self, value): + """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, + ): + raise ValueError("tileSize only work in JPEG2000. Please change PDFSaveOptions.encoding to JPEG2000.") + self.app.tileSize = value + + @property + def view(self): + """If true, opens the saved PDF in Acrobat.""" + return self.app.view + + @view.setter + def view(self, value): + """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 658b132b..5438b336 100644 --- a/photoshop/api/save_options/png.py +++ b/photoshop/api/save_options/png.py @@ -1,96 +1,97 @@ -# Import local modules -from photoshop.api._core import Photoshop -from photoshop.api.enumerations import ColorReductionType -from photoshop.api.enumerations import DitherType - - -class ExportOptionsSaveForWeb(Photoshop): - """Options for exporting Save For Web files.""" - - object_name = "ExportOptionsSaveForWeb" - - def __init__(self): - super().__init__() - self.format = 13 # PNG - self.PNG8 = False # Sets it to PNG-24 bit - - @property - def PNG8(self) -> bool: - """If true, uses 8 bits. If false, uses 24 bits. Valid only when ‘format’ = PNG.""" - return self.app.PNG8 - - @PNG8.setter - def PNG8(self, value: bool): - self.app.PNG8 = value - - @property - def blur(self) -> float: - """Applies blur to the image to reduce artifacts.""" - return self.app.blur - - @blur.setter - def blur(self, value: float): - self.app.blur = value - - @property - def colorReduction(self) -> ColorReductionType: - """The color reduction algorithm.""" - return self.app.colorReduction - - @colorReduction.setter - def colorReduction(self, value: ColorReductionType): - self.app.colorReduction = value - - @property - def colors(self) -> int: - """The number of colors in the palette.""" - return self.app.colors - - @colors.setter - def colors(self, value: int): - self.app.colors = value - - @property - def dither(self) -> DitherType: - """The type of dither to use.""" - return self.app.dither - - @dither.setter - def dither(self, value: DitherType): - self.app.dither = value - - @property - def quality(self) -> int: - """The quality of the output image, from 0 to 100.""" - return self.app.quality - - @quality.setter - def quality(self, value: int): - self.app.quality = value - - -class PNGSaveOptions(Photoshop): - """Options for saving file as PNG.""" - - object_name = "PNGSaveOptions" - - def __init__(self, interlaced: bool = False, compression: int = 6): - super().__init__() - self.interlaced = interlaced - self.compression = compression - - @property - def interlaced(self) -> bool: - return self.app.interlaced - - @interlaced.setter - def interlaced(self, value: bool): - self.app.interlaced = value - - @property - def compression(self) -> int: - return self.app.compression - - @compression.setter - def compression(self, value: int): - self.app.compression = value +# Import local modules +from __future__ import annotations + +from photoshop.api._core import Photoshop +from photoshop.api.enumerations import ColorReductionType, DitherType + + +class ExportOptionsSaveForWeb(Photoshop): + """Options for exporting Save For Web files.""" + + object_name = "ExportOptionsSaveForWeb" + + def __init__(self): + super().__init__() + self.format = 13 # PNG + self.PNG8 = False # Sets it to PNG-24 bit + + @property + def PNG8(self) -> bool: + """If true, uses 8 bits. If false, uses 24 bits. Valid only when ‘format’ = PNG.""" + return self.app.PNG8 + + @PNG8.setter + def PNG8(self, value: bool): + self.app.PNG8 = value + + @property + def blur(self) -> float: + """Applies blur to the image to reduce artifacts.""" + return self.app.blur + + @blur.setter + def blur(self, value: float): + self.app.blur = value + + @property + def colorReduction(self) -> ColorReductionType: + """The color reduction algorithm.""" + return self.app.colorReduction + + @colorReduction.setter + def colorReduction(self, value: ColorReductionType): + self.app.colorReduction = value + + @property + def colors(self) -> int: + """The number of colors in the palette.""" + return self.app.colors + + @colors.setter + def colors(self, value: int): + self.app.colors = value + + @property + def dither(self) -> DitherType: + """The type of dither to use.""" + return self.app.dither + + @dither.setter + def dither(self, value: DitherType): + self.app.dither = value + + @property + def quality(self) -> int: + """The quality of the output image, from 0 to 100.""" + return self.app.quality + + @quality.setter + def quality(self, value: int): + self.app.quality = value + + +class PNGSaveOptions(Photoshop): + """Options for saving file as PNG.""" + + object_name = "PNGSaveOptions" + + def __init__(self, interlaced: bool = False, compression: int = 6): + super().__init__() + self.interlaced = interlaced + self.compression = compression + + @property + def interlaced(self) -> bool: + return self.app.interlaced + + @interlaced.setter + def interlaced(self, value: bool): + self.app.interlaced = value + + @property + def compression(self) -> int: + return self.app.compression + + @compression.setter + def compression(self, value: int): + self.app.compression = value diff --git a/photoshop/api/save_options/psd.py b/photoshop/api/save_options/psd.py index 2cb79233..b361b4ff 100644 --- a/photoshop/api/save_options/psd.py +++ b/photoshop/api/save_options/psd.py @@ -1,56 +1,58 @@ -# Import local modules -from photoshop.api._core import Photoshop - - -class PhotoshopSaveOptions(Photoshop): - """Options for saving a Photoshop document.""" - - object_name = "PhotoshopSaveOptions" - - def __int__(self): - super().__init__() - - @property - def alphaChannels(self): - """If true, the alpha channels are saved.""" - return self.app.alphaChannels() - - @alphaChannels.setter - def alphaChannels(self, value): - self.app.alphaChannels = value - - @property - def annotations(self): - """If true, the annotations are saved.""" - return self.app.annotations() - - @annotations.setter - def annotations(self, value): - self.app.annotations = value - - @property - def embedColorProfile(self): - """If true, the color profile is embedded in the document.""" - return self.app.embedColorProfile() - - @embedColorProfile.setter - def embedColorProfile(self, value): - self.app.embedColorProfile = value - - @property - def layers(self): - """If true, the layers are saved.""" - return self.app.layers() - - @layers.setter - def layers(self, value): - self.app.layers = value - - @property - def spotColors(self): - """If true, spot colors are saved.""" - return self.app.spotColors() - - @spotColors.setter - def spotColors(self, value): - self.app.spotColors = value +# Import local modules +from __future__ import annotations + +from photoshop.api._core import Photoshop + + +class PhotoshopSaveOptions(Photoshop): + """Options for saving a Photoshop document.""" + + object_name = "PhotoshopSaveOptions" + + def __int__(self): + super().__init__() + + @property + def alphaChannels(self): + """If true, the alpha channels are saved.""" + return self.app.alphaChannels() + + @alphaChannels.setter + def alphaChannels(self, value): + self.app.alphaChannels = value + + @property + def annotations(self): + """If true, the annotations are saved.""" + return self.app.annotations() + + @annotations.setter + def annotations(self, value): + self.app.annotations = value + + @property + def embedColorProfile(self): + """If true, the color profile is embedded in the document.""" + return self.app.embedColorProfile() + + @embedColorProfile.setter + def embedColorProfile(self, value): + self.app.embedColorProfile = value + + @property + def layers(self): + """If true, the layers are saved.""" + return self.app.layers() + + @layers.setter + def layers(self, value): + self.app.layers = value + + @property + def spotColors(self): + """If true, spot colors are saved.""" + return self.app.spotColors() + + @spotColors.setter + def spotColors(self, value): + self.app.spotColors = value diff --git a/photoshop/api/save_options/tag.py b/photoshop/api/save_options/tag.py index 622170b6..b8d3734c 100644 --- a/photoshop/api/save_options/tag.py +++ b/photoshop/api/save_options/tag.py @@ -1,37 +1,39 @@ -# Import local modules -from photoshop.api._core import Photoshop -from photoshop.api.enumerations import TargaBitsPerPixels - - -class TargaSaveOptions(Photoshop): - """Options for saving a document in TGA (Targa) format.""" - - object_name = "TargaSaveOptions" - - def __int__(self): - super().__init__() - - @property - def alphaChannels(self): - """If true, the alpha channels are saved.""" - return self.app.alphaChannels - - @alphaChannels.setter - def alphaChannels(self, value): - self.app.alphaChannels = value - - @property - def resolution(self): - return self.app.resolution - - @resolution.setter - def resolution(self, value: TargaBitsPerPixels = TargaBitsPerPixels.Targa24Bits): - self.app.resolution = value - - @property - def rleCompression(self): - return self.app.rleCompression - - @rleCompression.setter - def rleCompression(self, value): - self.app.rleCompression = value +# Import local modules +from __future__ import annotations + +from photoshop.api._core import Photoshop +from photoshop.api.enumerations import TargaBitsPerPixels + + +class TargaSaveOptions(Photoshop): + """Options for saving a document in TGA (Targa) format.""" + + object_name = "TargaSaveOptions" + + def __int__(self): + super().__init__() + + @property + def alphaChannels(self): + """If true, the alpha channels are saved.""" + return self.app.alphaChannels + + @alphaChannels.setter + def alphaChannels(self, value): + self.app.alphaChannels = value + + @property + def resolution(self): + return self.app.resolution + + @resolution.setter + def resolution(self, value: TargaBitsPerPixels = TargaBitsPerPixels.Targa24Bits): + self.app.resolution = value + + @property + def rleCompression(self): + return self.app.rleCompression + + @rleCompression.setter + def rleCompression(self, value): + self.app.rleCompression = value diff --git a/photoshop/api/save_options/tif.py b/photoshop/api/save_options/tif.py index 79f5379c..64e1f389 100644 --- a/photoshop/api/save_options/tif.py +++ b/photoshop/api/save_options/tif.py @@ -1,129 +1,132 @@ -# Import local modules -from photoshop.api._core import Photoshop - - -class TiffSaveOptions(Photoshop): - """Options for saving a document in TIFF format.""" - - object_name = "TiffSaveOptions" - - def __int__(self): - super().__init__() - - @property - def alphaChannels(self): - """If true, the alpha channels are saved.""" - return self.app.alphaChannels - - @alphaChannels.setter - def alphaChannels(self, value): - self.app.alphaChannels = value - - @property - def annotations(self): - """If true, the annotations are saved.""" - return self.app.annotations - - @annotations.setter - def annotations(self, value): - self.app.annotations = value - - @property - def byteOrder(self): - """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 - - @byteOrder.setter - def byteOrder(self, value): - self.app.byteOrder = value - - @property - def embedColorProfile(self): - """If true, the color profile is embedded in the document.""" - return self.app.embedColorProfile - - @embedColorProfile.setter - def embedColorProfile(self, value): - self.app.embedColorProfile = value - - @property - def imageCompression(self): - """The compression type.""" - return self.app.imageCompression - - @imageCompression.setter - def imageCompression(self, value): - self.app.imageCompression = value - - @property - def interleaveChannels(self): - """If true, the channels in the image are interleaved.""" - return self.app.interleaveChannels - - @interleaveChannels.setter - def interleaveChannels(self, value): - self.app.interleaveChannels = value - - @property - def jpegQuality(self): - """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. - """ - return self.app.jpegQuality - - @jpegQuality.setter - def jpegQuality(self, value): - self.app.jpegQuality = value - - @property - def layerCompression(self): - return self.app.layerCompression - - @layerCompression.setter - def layerCompression(self, value): - """The method of compression to use when saving layers - (as opposed to saving composite data). - Valid only when `layers` = true. - """ - self.app.layerCompression = value - - @property - def layers(self): - """If true, the layers are saved.""" - return self.app.layers - - @layers.setter - def layers(self, value): - self.app.layers = value - - @property - def saveImagePyramid(self): - """If true, preserves multi-resolution information.""" - return self.app.saveImagePyramid - - @saveImagePyramid.setter - def saveImagePyramid(self, value): - self.app.saveImagePyramid = value - - @property - def spotColors(self): - """If true, spot colors are saved.""" - return self.app.spotColors - - @spotColors.setter - def spotColors(self, value): - self.app.spotColors = value - - @property - def transparency(self): - return self.app.transparency - - @transparency.setter - def transparency(self, value): - """If true, saves the transparency as an additional alpha channel when - the file is opened in another application.""" - self.app.transparency = value +# Import local modules +from __future__ import annotations + +from photoshop.api._core import Photoshop + + +class TiffSaveOptions(Photoshop): + """Options for saving a document in TIFF format.""" + + object_name = "TiffSaveOptions" + + def __int__(self): + super().__init__() + + @property + def alphaChannels(self): + """If true, the alpha channels are saved.""" + return self.app.alphaChannels + + @alphaChannels.setter + def alphaChannels(self, value): + self.app.alphaChannels = value + + @property + def annotations(self): + """If true, the annotations are saved.""" + return self.app.annotations + + @annotations.setter + def annotations(self, value): + self.app.annotations = value + + @property + def byteOrder(self): + """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 + + @byteOrder.setter + def byteOrder(self, value): + self.app.byteOrder = value + + @property + def embedColorProfile(self): + """If true, the color profile is embedded in the document.""" + return self.app.embedColorProfile + + @embedColorProfile.setter + def embedColorProfile(self, value): + self.app.embedColorProfile = value + + @property + def imageCompression(self): + """The compression type.""" + return self.app.imageCompression + + @imageCompression.setter + def imageCompression(self, value): + self.app.imageCompression = value + + @property + def interleaveChannels(self): + """If true, the channels in the image are interleaved.""" + return self.app.interleaveChannels + + @interleaveChannels.setter + def interleaveChannels(self, value): + self.app.interleaveChannels = value + + @property + def jpegQuality(self): + """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. + """ + return self.app.jpegQuality + + @jpegQuality.setter + def jpegQuality(self, value): + self.app.jpegQuality = value + + @property + def layerCompression(self): + return self.app.layerCompression + + @layerCompression.setter + def layerCompression(self, value): + """The method of compression to use when saving layers + (as opposed to saving composite data). + Valid only when `layers` = true. + """ + self.app.layerCompression = value + + @property + def layers(self): + """If true, the layers are saved.""" + return self.app.layers + + @layers.setter + def layers(self, value): + self.app.layers = value + + @property + def saveImagePyramid(self): + """If true, preserves multi-resolution information.""" + return self.app.saveImagePyramid + + @saveImagePyramid.setter + def saveImagePyramid(self, value): + self.app.saveImagePyramid = value + + @property + def spotColors(self): + """If true, spot colors are saved.""" + return self.app.spotColors + + @spotColors.setter + def spotColors(self, value): + self.app.spotColors = value + + @property + def transparency(self): + return self.app.transparency + + @transparency.setter + def transparency(self, value): + """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..6140cf9e 100644 --- a/photoshop/api/solid_color.py +++ b/photoshop/api/solid_color.py @@ -1,92 +1,94 @@ -"""A color definition used in the document. - -Maps a color to equivalents in all available color models. - -- Used in `Application.backgroundColor` and `foregroundColor` properties, in -`Channel.color`, in `ColorSampler.color`, and in `TextItem.color` -- Passed to `PathItem.fillPath()`, `Selection.fill()`, and `Selection.stroke()`. - -""" - -# Import local modules -from photoshop.api._core import Photoshop -from photoshop.api.colors.cmyk import CMYKColor -from photoshop.api.colors.gray import GrayColor -from photoshop.api.colors.hsb import HSBColor -from photoshop.api.colors.lab import LabColor -from photoshop.api.colors.rgb import RGBColor -from photoshop.api.enumerations import ColorModel - - -class SolidColor(Photoshop): - """A color definition used in the document.""" - - object_name = "SolidColor" - - def __init__(self, parent=None): - super().__init__(parent=parent) - self._flag_as_method( - "isEqual", - ) - - @property - def cmyk(self) -> CMYKColor: - """The CMYK color mode. - - Returns: - .colors.cmyk.CMYKColor: - - """ - return CMYKColor(self.app.cmyk) - - @cmyk.setter - def cmyk(self, value: CMYKColor): - self.app.cmyk = value - - @property - def gray(self) -> GrayColor: - return GrayColor(self.app.gray) - - @property - def hsb(self) -> HSBColor: - return HSBColor(self.app.hsb) - - @hsb.setter - def hsb(self, value: HSBColor): - self.app.hsb = value - - @property - def lab(self) -> LabColor: - return LabColor(self.app.lab) - - @lab.setter - def lab(self, value: LabColor): - self.app.lab = value - - @property - def model(self) -> ColorModel: - """The color model.""" - return ColorModel(self.app.model) - - @model.setter - def model(self, value: ColorModel): - """The color model.""" - self.app.model = value - - @property - def nearestWebColor(self) -> RGBColor: - """The nearest web color to the current color.""" - return RGBColor(self.app.NearestWebColor) - - @property - def rgb(self) -> RGBColor: - """The RGB color mode.""" - return RGBColor(self.app.rgb) - - @rgb.setter - def rgb(self, value: RGBColor): - self.app.rgb = value - - def isEqual(self, color: RGBColor): - """`SolidColor` object is visually equal to the specified color.""" - return self.app.isEqual(color) +"""A color definition used in the document. + +Maps a color to equivalents in all available color models. + +- Used in `Application.backgroundColor` and `foregroundColor` properties, in +`Channel.color`, in `ColorSampler.color`, and in `TextItem.color` +- Passed to `PathItem.fillPath()`, `Selection.fill()`, and `Selection.stroke()`. + +""" + +# Import local modules +from __future__ import annotations + +from photoshop.api._core import Photoshop +from photoshop.api.colors.cmyk import CMYKColor +from photoshop.api.colors.gray import GrayColor +from photoshop.api.colors.hsb import HSBColor +from photoshop.api.colors.lab import LabColor +from photoshop.api.colors.rgb import RGBColor +from photoshop.api.enumerations import ColorModel + + +class SolidColor(Photoshop): + """A color definition used in the document.""" + + object_name = "SolidColor" + + def __init__(self, parent=None): + super().__init__(parent=parent) + self._flag_as_method( + "isEqual", + ) + + @property + def cmyk(self) -> CMYKColor: + """The CMYK color mode. + + Returns: + .colors.cmyk.CMYKColor: + + """ + return CMYKColor(self.app.cmyk) + + @cmyk.setter + def cmyk(self, value: CMYKColor): + self.app.cmyk = value + + @property + def gray(self) -> GrayColor: + return GrayColor(self.app.gray) + + @property + def hsb(self) -> HSBColor: + return HSBColor(self.app.hsb) + + @hsb.setter + def hsb(self, value: HSBColor): + self.app.hsb = value + + @property + def lab(self) -> LabColor: + return LabColor(self.app.lab) + + @lab.setter + def lab(self, value: LabColor): + self.app.lab = value + + @property + def model(self) -> ColorModel: + """The color model.""" + return ColorModel(self.app.model) + + @model.setter + def model(self, value: ColorModel): + """The color model.""" + self.app.model = value + + @property + def nearestWebColor(self) -> RGBColor: + """The nearest web color to the current color.""" + return RGBColor(self.app.NearestWebColor) + + @property + def rgb(self) -> RGBColor: + """The RGB color mode.""" + return RGBColor(self.app.rgb) + + @rgb.setter + def rgb(self, value: RGBColor): + self.app.rgb = value + + def isEqual(self, color: RGBColor): + """`SolidColor` object is visually equal to the specified color.""" + return self.app.isEqual(color) diff --git a/photoshop/api/text_font.py b/photoshop/api/text_font.py index c5e83f82..ab413f8d 100644 --- a/photoshop/api/text_font.py +++ b/photoshop/api/text_font.py @@ -1,29 +1,31 @@ -# Import local modules -from photoshop.api._core import Photoshop - - -class TextFont(Photoshop): - """An installed font.""" - - def __init__(self, parent=None): - super().__init__(parent=parent) - - @property - def family(self) -> str: - """The font family""" - return self.app.family - - @property - def name(self): - """The name of the font.""" - return self.app.name - - @property - def postScriptName(self): - """The PostScript name of the font.""" - return self.app.postScriptName - - @property - def style(self): - """The font style.""" - return self.app.style +# Import local modules +from __future__ import annotations + +from photoshop.api._core import Photoshop + + +class TextFont(Photoshop): + """An installed font.""" + + def __init__(self, parent=None): + super().__init__(parent=parent) + + @property + def family(self) -> str: + """The font family""" + return self.app.family + + @property + def name(self): + """The name of the font.""" + return self.app.name + + @property + def postScriptName(self): + """The PostScript name of the font.""" + return self.app.postScriptName + + @property + def style(self): + """The font style.""" + return self.app.style diff --git a/photoshop/api/text_item.py b/photoshop/api/text_item.py index 38b338d3..a8f1d991 100644 --- a/photoshop/api/text_item.py +++ b/photoshop/api/text_item.py @@ -1,693 +1,702 @@ -# Import local modules -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.solid_color import SolidColor - - -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): - super().__init__(parent=parent) - self._flag_as_method( - "convertToShape", - "createPath", - ) - - @property - def alternateLigatures(self): - return self.app.alternateLigatures - - @alternateLigatures.setter - def alternateLigatures(self, value): - self.app.alternateLigatures = value - - @property - def antiAliasMethod(self) -> AntiAlias: - """The method of antialiasing to use.""" - return AntiAlias(self.app.antiAliasMethod) - - @antiAliasMethod.setter - def antiAliasMethod(self, value): - self.app.antiAliasMethod = value - - @property - def autoKerning(self) -> AutoKernType: - """The auto kerning option to use.""" - return AutoKernType(self.app.autoKerning) - - @autoKerning.setter - def autoKerning(self, value): - self.app.autoKerning = value - - @property - def autoLeadingAmount(self): - return self.app.autoLeadingAmount - - @autoLeadingAmount.setter - def autoLeadingAmount(self, value): - """The percentage to use for auto (default) leading (in points). - - Valid only when useAutoLeading = True. - - """ - self.app.useAutoLeading = True - self.app.autoLeadingAmount = value - - @property - def baselineShift(self): - """The unit value to use in the baseline offset of text.""" - return self.app.baselineShift - - @baselineShift.setter - def baselineShift(self, value): - self.app.baselineShift = value - - @property - def capitalization(self): - """Gets text case.""" - return self.app.capitalization - - @capitalization.setter - def capitalization(self, value): - """Sets text case.""" - self.app.capitalization = value - - @property - def color(self) -> SolidColor: - """The text color.""" - return SolidColor(self.app.color) - - @color.setter - def color(self, color_value): - """The color of textItem.""" - self.app.color = color_value - - @property - def contents(self) -> str: - """The actual text in the layer.""" - return self.app.contents - - @contents.setter - def contents(self, text: str): - """Set the actual text in the layer. - - Args: - text: The actual text. - - """ - self.app.contents = text - - @property - def desiredGlyphScaling(self): - """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): - self.app.desiredGlyphScaling = value - - @property - def desiredLetterScaling(self): - """The amount of space between letters . - (at 0, no space is added between letters). - Equivalent to Letter Spacing in the Justification - dialog (Select Justification on the Paragraphs palette menu). - Valid only when justification = Justification.CENTERJUSTIFIED, - FULLYJUSTIFIED, - LEFTJUSTIFIED, or - Justification.RIGHTJUSTIFIED. - When used, the minimumLetterScaling and - maximumLetterScaling values are also required. - - """ - return self.app.desiredLetterScaling - - @desiredLetterScaling.setter - def desiredLetterScaling(self, value): - self.app.desiredGlyphScaling = value - - @property - def desiredWordScaling(self): - """ - The amount (percentage) of space - between words (at 100, no additional space is added - between words). - Equivalent to Word Spacing in the Justification - dialog (Select Justification on the Paragraphs - palette menu). - Valid only when justification = - Justification.CENTERJUSTIFIED, - FULLYJUSTIFIED, LEFTJUSTIFIED, or - Justification.RIGHTJUSTIFIED. - When used, the minimumWordScaling and - maximumWordScaling values are also required - - """ - return self.app.desiredWordScaling - - @desiredWordScaling.setter - def desiredWordScaling(self, value): - self.app.desiredWordScaling = value - - @property - def direction(self): - """The text orientation.""" - return Direction(self.app.direction) - - @direction.setter - def direction(self, value): - self.app.direction = value - - @property - def fauxBold(self): - """True to use faux bold (default: false). - - Setting this to true is equivalent to selecting text and - clicking Faux Bold in the Character palette. - - """ - return self.app.fauxBold - - @fauxBold.setter - def fauxBold(self, value): - self.app.fauxBold = value - - @property - def fauxItalic(self): - """True to use faux italic (default: false). - - Setting this to true is equivalent to selecting text and - clicking Faux Italic in the Character palette. - - """ - return self.app.fauxItalic - - @fauxItalic.setter - def fauxItalic(self, value): - self.app.fauxItalic = value - - @property - def firstLineIndent(self): - """The amount (unit value) to indent the first line of paragraphs.""" - return self.app.firstLineIndent - - @firstLineIndent.setter - def firstLineIndent(self, value): - self.app.firstLineIndent = value - - @property - def font(self) -> str: - """str: postScriptName of the TextItem's font.""" - return self.app.font - - @font.setter - def font(self, text_font: str): - """Set the font of this TextItem. - - Args: - text_font (str): Must provide the postScriptName of a valid font. - """ - self.app.font = text_font - - @property - def hangingPunctuation(self) -> bool: - """bool: Whether to use Roman hanging punctuation.""" - return self.app.hangingPunctuation - - @hangingPunctuation.setter - def hangingPunctuation(self, value: bool): - self.app.hangingPunctuation = value - - @property - def height(self): - """int: The height of the bounding box for paragraph text.""" - return self.app.height - - @height.setter - def height(self, value): - self.app.height = value - - @property - def horizontalScale(self) -> int: - """Character scaling (horizontal) in proportion to verticalScale (a percentage value).""" - return self.app.horizontalScale - - @horizontalScale.setter - def horizontalScale(self, value: int): - """Set the horizontalScale of this TextItem. - - Args: - value: An integer between 0 and 1000. - """ - self.app.horizontalScale = value - - @property - def hyphenateAfterFirst(self): - """The number of letters after which hyphenation in word wrap is allowed.""" - return self.app.hyphenateAfterFirst - - @hyphenateAfterFirst.setter - def hyphenateAfterFirst(self, value): - self.app.hyphenateAfterFirst = value - - @property - def hyphenateBeforeLast(self): - """The number of letters before which hyphenation in word wrap is allowed.""" - return self.app.hyphenateBeforeLast - - @hyphenateBeforeLast.setter - def hyphenateBeforeLast(self, value): - self.app.hyphenateBeforeLast = value - - @property - def hyphenateCapitalWords(self): - """True to allow hyphenation in word wrap of capitalized words""" - return self.app.hyphenateCapitalWords - - @hyphenateCapitalWords.setter - def hyphenateCapitalWords(self, value): - self.app.hyphenateCapitalWords = value - - @property - def hyphenateWordsLongerThan(self): - """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): - self.app.hyphenateWordsLongerThan = value - - @property - def hyphenation(self): - """True to use hyphenation in word wrap.""" - return self.app.hyphenation - - @hyphenation.setter - def hyphenation(self, value): - self.app.hyphenation = value - - @property - def hyphenationZone(self): - """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): - self.app.hyphenationZone = value - - @property - def hyphenLimit(self): - return self.app.hyphenLimit - - @hyphenLimit.setter - def hyphenLimit(self, value): - self.app.hyphenLimit = value - - @property - def justification(self): - """The paragraph justification.""" - return Justification(self.app.justification) - - @justification.setter - def justification(self, value): - self.app.justification = value - - @property - def kind(self): - return TextType(self.app.kind) - - @kind.setter - def kind(self, kind_type): - self.app.kind = kind_type - - @property - def language(self): - return Language(self.app.language) - - @language.setter - def language(self, text): - self.app.language = text - - @property - def leading(self): - return self.app.leading - - @leading.setter - def leading(self, value): - self.app.leading = value - - @property - def leftIndent(self): - """The amoun of space to indent text from the left.""" - return self.app.leftIndent - - @leftIndent.setter - def leftIndent(self, value): - self.app.leftIndent = value - - @property - def ligatures(self): - """True to use ligatures.""" - return self.app.ligatures - - @ligatures.setter - def ligatures(self, value): - self.app.ligatures = value - - @property - def maximumGlyphScaling(self): - """The maximum amount to scale the horizontal size of the text letters - (a percentage value; at 100, the width of characters is not scaled). - - Valid only when justification = - Justification.CENTERJUSTIFIED, - FULLYJUSTIFIED, LEFTJUSTIFIED, or - Justification.RIGHTJUSTIFIED. - When used, the minimumGlyphScaling and - desiredGlyphScaling values are also required. - - """ - return self.app.maximumGlyphScaling - - @maximumGlyphScaling.setter - def maximumGlyphScaling(self, value): - self.app.maximumGlyphScaling = value - - @property - def maximumLetterScaling(self): - """The maximum amount of space to allow between letters - - (at 0, no space is added between letters). - Equivalent to Letter Spacing in the Justification - dialog (Select Justification on the Paragraphs - palette menu). - Valid only when justification = - Justification.CENTERJUSTIFIED, - FULLYJUSTIFIED, LEFTJUSTIFIED, or - Justification.RIGHTJUSTIFIED. - When used, the minimumLetterScaling and - desiredLetterScaling values are also required. - - """ - return self.app.maximumLetterScaling - - @maximumLetterScaling.setter - def maximumLetterScaling(self, value): - self.app.maximumLetterScaling = value - - @property - def maximumWordScaling(self): - return self.app.maximumWordScaling - - @maximumWordScaling.setter - def maximumWordScaling(self, value): - self.app.maximumWordScaling = value - - @property - def minimumGlyphScaling(self): - """The minimum amount to scale the horizontal size of the text letters - (a percentage value; at 100, the width of characters is not scaled). - - Valid only when justification = - Justification.CENTERJUSTIFIED, - FULLYJUSTIFIED, LEFTJUSTIFIED, or - Justification.RIGHTJUSTIFIED. - When used, the maximumGlyphScaling and - desiredGlyphScaling values are also required. - - """ - return self.app.minimumGlyphScaling - - @minimumGlyphScaling.setter - def minimumGlyphScaling(self, value): - self.app.minimumGlyphScaling = value - - @property - def minimumLetterScaling(self): - """The minimum amount of space to allow between letters - - (a percentage value; at 0, no space is removed between letters). - - Equivalent to Letter Spacing in the Justification - dialog (Select Justification on the Paragraphs - palette menu). - Valid only when justification = - Justification.CENTERJUSTIFIED, - FULLYJUSTIFIED, LEFTJUSTIFIED, or - Justification.RIGHTJUSTIFIED. - When used, the maximumLetterScaling and - desiredLetterScaling values are also required. - - """ - return self.app.minimumLetterScaling - - @minimumLetterScaling.setter - def minimumLetterScaling(self, value): - self.app.minimumLetterScaling = value - - @property - def minimumWordScaling(self): - """The minimum amount of space to allow between words - - (a percentage value; at 100, no additional space is removed between words). - - Equivalent to Word Spacing in the Justification - dialog (Select Justification on the Paragraphs - palette menu). - Valid only when justification = - Justification.CENTERJUSTIFIED, - FULLYJUSTIFIED, LEFTJUSTIFIED, or - Justification.RIGHTJUSTIFIED. - When used, the maximumWordScaling and - desiredWordScaling values are also required. - - """ - return self.app.minimumWordScaling - - @minimumWordScaling.setter - def minimumWordScaling(self, value): - self.app.minimumWordScaling = value - - @property - def noBreak(self): - """True to disallow line breaks in this text. - - Tip: When true for many consecutive characters, can - prevent word wrap and thus may prevent some - text from appearing on the screen. - - """ - return self.app.noBreak - - @noBreak.setter - def noBreak(self, value): - self.app.noBreak = value - - @property - def oldStyle(self): - return self.app.oldStyle - - @oldStyle.setter - def oldStyle(self, value): - self.app.oldStyle = value - - @property - def parent(self): - return self.app.parent - - @parent.setter - def parent(self, value): - self.app.parent = value - - @property - def position(self): - return self.app.position - - @position.setter - def position(self, array): - """The position of the origin for the text. - - The array must contain two values. Setting this property is basically - equivalent to clicking the text tool at a point in the documents to - create the point of origin for text. - - """ - self.app.position = array - - @property - def rightIndent(self): - return self.app.rightIndent - - @rightIndent.setter - def rightIndent(self, value): - self.app.rightIndent = value - - @property - def size(self): - """The font size in UnitValue. - - NOTE: Type was points for CS3 and older. - - """ - return self.app.size - - @size.setter - def size(self, value): - self.app.size = value - - @property - def spaceAfter(self): - """The amount of space to use after each paragraph.""" - return self.app.spaceAfter - - @spaceAfter.setter - def spaceAfter(self, value): - self.app.spaceAfter = value - - @property - def spaceBefore(self): - return self.app.spaceBefore - - @spaceBefore.setter - def spaceBefore(self, value): - self.app.spaceBefore = value - - @property - def strikeThru(self): - """The text strike-through option to use.""" - return StrikeThruType(self.app.strikeThru) - - @strikeThru.setter - def strikeThru(self, value): - self.app.strikeThru = value - - @property - def textComposer(self): - return TextComposer(self.app.textComposer) - - @textComposer.setter - def textComposer(self, value): - self.app.textComposer = value - - @property - def tracking(self): - return self.app.tracking - - @tracking.setter - def tracking(self, value): - self.app.tracking = value - - @property - def underline(self): - """The text underlining options.""" - return self.app.underline - - @underline.setter - def underline(self, value): - self.app.underline = value - - @property - def useAutoLeading(self): - return self.app.useAutoLeading - - @useAutoLeading.setter - def useAutoLeading(self, value): - self.app.useAutoLeading = value - - @property - def verticalScale(self): - return self.app.verticalScale - - @verticalScale.setter - def verticalScale(self, value): - self.app.verticalScale = value - - @property - def warpBend(self): - """The warp bend percentage.""" - return self.app.warpBend - - @warpBend.setter - def warpBend(self, value): - self.app.warpBend = value - - @property - def warpDirection(self) -> Direction: - """The warp direction.""" - return Direction(self.app.warpDirection) - - @warpDirection.setter - def warpDirection(self, value): - self.app.warpDirection = value - - @property - def warpHorizontalDistortion(self): - return self.app.warpHorizontalDistortion - - @warpHorizontalDistortion.setter - def warpHorizontalDistortion(self, value): - self.app.warpHorizontalDistortion = value - - @property - def warpStyle(self): - """The style of warp to use.""" - return self.app.warpStyle - - @warpStyle.setter - def warpStyle(self, value): - self.app.warpStyle = value - - @property - def warpVerticalDistortion(self): - return self.app.warpVerticalDistortion - - @warpVerticalDistortion.setter - def warpVerticalDistortion(self, value): - self.app.warpVerticalDistortion = value - - @property - def width(self): - """The width of the bounding box for - - paragraph text. - Valid only when kind = TextType.PARAGRAPHTEXT. - - """ - return self.app.width - - @width.setter - def width(self, value: float): - """The width of the bounding box for - - paragraph text. - Valid only when kind = TextType.PARAGRAPHTEXT. - - """ - self.app.width = value - - def convertToShape(self): - """Converts the text item and its containing layer to a fill layer with the - text changed to a clipping path.""" - return self.app.convertToShape() - - def createPath(self): - """Creates a clipping path from the outlines of the actual text items - - (such as letters or words). - - """ - return self.app.createPath() +# Import local modules +from __future__ import annotations + +from photoshop.api._core import Photoshop +from photoshop.api.enumerations import ( + AntiAlias, + AutoKernType, + Direction, + Justification, + Language, + StrikeThruType, + TextComposer, + TextType, +) +from photoshop.api.solid_color import SolidColor + + +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): + super().__init__(parent=parent) + self._flag_as_method( + "convertToShape", + "createPath", + ) + + @property + def alternateLigatures(self): + return self.app.alternateLigatures + + @alternateLigatures.setter + def alternateLigatures(self, value): + self.app.alternateLigatures = value + + @property + def antiAliasMethod(self) -> AntiAlias: + """The method of antialiasing to use.""" + return AntiAlias(self.app.antiAliasMethod) + + @antiAliasMethod.setter + def antiAliasMethod(self, value): + self.app.antiAliasMethod = value + + @property + def autoKerning(self) -> AutoKernType: + """The auto kerning option to use.""" + return AutoKernType(self.app.autoKerning) + + @autoKerning.setter + def autoKerning(self, value): + self.app.autoKerning = value + + @property + def autoLeadingAmount(self): + return self.app.autoLeadingAmount + + @autoLeadingAmount.setter + def autoLeadingAmount(self, value): + """The percentage to use for auto (default) leading (in points). + + Valid only when useAutoLeading = True. + + """ + self.app.useAutoLeading = True + self.app.autoLeadingAmount = value + + @property + def baselineShift(self): + """The unit value to use in the baseline offset of text.""" + return self.app.baselineShift + + @baselineShift.setter + def baselineShift(self, value): + self.app.baselineShift = value + + @property + def capitalization(self): + """Gets text case.""" + return self.app.capitalization + + @capitalization.setter + def capitalization(self, value): + """Sets text case.""" + self.app.capitalization = value + + @property + def color(self) -> SolidColor: + """The text color.""" + return SolidColor(self.app.color) + + @color.setter + def color(self, color_value): + """The color of textItem.""" + self.app.color = color_value + + @property + def contents(self) -> str: + """The actual text in the layer.""" + return self.app.contents + + @contents.setter + def contents(self, text: str): + """Set the actual text in the layer. + + Args: + text: The actual text. + + """ + self.app.contents = text + + @property + def desiredGlyphScaling(self): + """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): + self.app.desiredGlyphScaling = value + + @property + def desiredLetterScaling(self): + """The amount of space between letters . + (at 0, no space is added between letters). + Equivalent to Letter Spacing in the Justification + dialog (Select Justification on the Paragraphs palette menu). + Valid only when justification = Justification.CENTERJUSTIFIED, + FULLYJUSTIFIED, + LEFTJUSTIFIED, or + Justification.RIGHTJUSTIFIED. + When used, the minimumLetterScaling and + maximumLetterScaling values are also required. + + """ + return self.app.desiredLetterScaling + + @desiredLetterScaling.setter + def desiredLetterScaling(self, value): + self.app.desiredGlyphScaling = value + + @property + def desiredWordScaling(self): + """The amount (percentage) of space + between words (at 100, no additional space is added + between words). + Equivalent to Word Spacing in the Justification + dialog (Select Justification on the Paragraphs + palette menu). + Valid only when justification = + Justification.CENTERJUSTIFIED, + FULLYJUSTIFIED, LEFTJUSTIFIED, or + Justification.RIGHTJUSTIFIED. + When used, the minimumWordScaling and + maximumWordScaling values are also required + + """ + return self.app.desiredWordScaling + + @desiredWordScaling.setter + def desiredWordScaling(self, value): + self.app.desiredWordScaling = value + + @property + def direction(self): + """The text orientation.""" + return Direction(self.app.direction) + + @direction.setter + def direction(self, value): + self.app.direction = value + + @property + def fauxBold(self): + """True to use faux bold (default: false). + + Setting this to true is equivalent to selecting text and + clicking Faux Bold in the Character palette. + + """ + return self.app.fauxBold + + @fauxBold.setter + def fauxBold(self, value): + self.app.fauxBold = value + + @property + def fauxItalic(self): + """True to use faux italic (default: false). + + Setting this to true is equivalent to selecting text and + clicking Faux Italic in the Character palette. + + """ + return self.app.fauxItalic + + @fauxItalic.setter + def fauxItalic(self, value): + self.app.fauxItalic = value + + @property + def firstLineIndent(self): + """The amount (unit value) to indent the first line of paragraphs.""" + return self.app.firstLineIndent + + @firstLineIndent.setter + def firstLineIndent(self, value): + self.app.firstLineIndent = value + + @property + def font(self) -> str: + """str: postScriptName of the TextItem's font.""" + return self.app.font + + @font.setter + def font(self, text_font: str): + """Set the font of this TextItem. + + Args: + text_font (str): Must provide the postScriptName of a valid font. + + """ + self.app.font = text_font + + @property + def hangingPunctuation(self) -> bool: + """bool: Whether to use Roman hanging punctuation.""" + return self.app.hangingPunctuation + + @hangingPunctuation.setter + def hangingPunctuation(self, value: bool): + self.app.hangingPunctuation = value + + @property + def height(self): + """int: The height of the bounding box for paragraph text.""" + return self.app.height + + @height.setter + def height(self, value): + self.app.height = value + + @property + def horizontalScale(self) -> int: + """Character scaling (horizontal) in proportion to verticalScale (a percentage value).""" + return self.app.horizontalScale + + @horizontalScale.setter + def horizontalScale(self, value: int): + """Set the horizontalScale of this TextItem. + + Args: + value: An integer between 0 and 1000. + + """ + self.app.horizontalScale = value + + @property + def hyphenateAfterFirst(self): + """The number of letters after which hyphenation in word wrap is allowed.""" + return self.app.hyphenateAfterFirst + + @hyphenateAfterFirst.setter + def hyphenateAfterFirst(self, value): + self.app.hyphenateAfterFirst = value + + @property + def hyphenateBeforeLast(self): + """The number of letters before which hyphenation in word wrap is allowed.""" + return self.app.hyphenateBeforeLast + + @hyphenateBeforeLast.setter + def hyphenateBeforeLast(self, value): + self.app.hyphenateBeforeLast = value + + @property + def hyphenateCapitalWords(self): + """True to allow hyphenation in word wrap of capitalized words""" + return self.app.hyphenateCapitalWords + + @hyphenateCapitalWords.setter + def hyphenateCapitalWords(self, value): + self.app.hyphenateCapitalWords = value + + @property + def hyphenateWordsLongerThan(self): + """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): + self.app.hyphenateWordsLongerThan = value + + @property + def hyphenation(self): + """True to use hyphenation in word wrap.""" + return self.app.hyphenation + + @hyphenation.setter + def hyphenation(self, value): + self.app.hyphenation = value + + @property + def hyphenationZone(self): + """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): + self.app.hyphenationZone = value + + @property + def hyphenLimit(self): + return self.app.hyphenLimit + + @hyphenLimit.setter + def hyphenLimit(self, value): + self.app.hyphenLimit = value + + @property + def justification(self): + """The paragraph justification.""" + return Justification(self.app.justification) + + @justification.setter + def justification(self, value): + self.app.justification = value + + @property + def kind(self): + return TextType(self.app.kind) + + @kind.setter + def kind(self, kind_type): + self.app.kind = kind_type + + @property + def language(self): + return Language(self.app.language) + + @language.setter + def language(self, text): + self.app.language = text + + @property + def leading(self): + return self.app.leading + + @leading.setter + def leading(self, value): + self.app.leading = value + + @property + def leftIndent(self): + """The amoun of space to indent text from the left.""" + return self.app.leftIndent + + @leftIndent.setter + def leftIndent(self, value): + self.app.leftIndent = value + + @property + def ligatures(self): + """True to use ligatures.""" + return self.app.ligatures + + @ligatures.setter + def ligatures(self, value): + self.app.ligatures = value + + @property + def maximumGlyphScaling(self): + """The maximum amount to scale the horizontal size of the text letters + (a percentage value; at 100, the width of characters is not scaled). + + Valid only when justification = + Justification.CENTERJUSTIFIED, + FULLYJUSTIFIED, LEFTJUSTIFIED, or + Justification.RIGHTJUSTIFIED. + When used, the minimumGlyphScaling and + desiredGlyphScaling values are also required. + + """ + return self.app.maximumGlyphScaling + + @maximumGlyphScaling.setter + def maximumGlyphScaling(self, value): + self.app.maximumGlyphScaling = value + + @property + def maximumLetterScaling(self): + """The maximum amount of space to allow between letters + + (at 0, no space is added between letters). + Equivalent to Letter Spacing in the Justification + dialog (Select Justification on the Paragraphs + palette menu). + Valid only when justification = + Justification.CENTERJUSTIFIED, + FULLYJUSTIFIED, LEFTJUSTIFIED, or + Justification.RIGHTJUSTIFIED. + When used, the minimumLetterScaling and + desiredLetterScaling values are also required. + + """ + return self.app.maximumLetterScaling + + @maximumLetterScaling.setter + def maximumLetterScaling(self, value): + self.app.maximumLetterScaling = value + + @property + def maximumWordScaling(self): + return self.app.maximumWordScaling + + @maximumWordScaling.setter + def maximumWordScaling(self, value): + self.app.maximumWordScaling = value + + @property + def minimumGlyphScaling(self): + """The minimum amount to scale the horizontal size of the text letters + (a percentage value; at 100, the width of characters is not scaled). + + Valid only when justification = + Justification.CENTERJUSTIFIED, + FULLYJUSTIFIED, LEFTJUSTIFIED, or + Justification.RIGHTJUSTIFIED. + When used, the maximumGlyphScaling and + desiredGlyphScaling values are also required. + + """ + return self.app.minimumGlyphScaling + + @minimumGlyphScaling.setter + def minimumGlyphScaling(self, value): + self.app.minimumGlyphScaling = value + + @property + def minimumLetterScaling(self): + """The minimum amount of space to allow between letters + + (a percentage value; at 0, no space is removed between letters). + + Equivalent to Letter Spacing in the Justification + dialog (Select Justification on the Paragraphs + palette menu). + Valid only when justification = + Justification.CENTERJUSTIFIED, + FULLYJUSTIFIED, LEFTJUSTIFIED, or + Justification.RIGHTJUSTIFIED. + When used, the maximumLetterScaling and + desiredLetterScaling values are also required. + + """ + return self.app.minimumLetterScaling + + @minimumLetterScaling.setter + def minimumLetterScaling(self, value): + self.app.minimumLetterScaling = value + + @property + def minimumWordScaling(self): + """The minimum amount of space to allow between words + + (a percentage value; at 100, no additional space is removed between words). + + Equivalent to Word Spacing in the Justification + dialog (Select Justification on the Paragraphs + palette menu). + Valid only when justification = + Justification.CENTERJUSTIFIED, + FULLYJUSTIFIED, LEFTJUSTIFIED, or + Justification.RIGHTJUSTIFIED. + When used, the maximumWordScaling and + desiredWordScaling values are also required. + + """ + return self.app.minimumWordScaling + + @minimumWordScaling.setter + def minimumWordScaling(self, value): + self.app.minimumWordScaling = value + + @property + def noBreak(self): + """True to disallow line breaks in this text. + + Tip: When true for many consecutive characters, can + prevent word wrap and thus may prevent some + text from appearing on the screen. + + """ + return self.app.noBreak + + @noBreak.setter + def noBreak(self, value): + self.app.noBreak = value + + @property + def oldStyle(self): + return self.app.oldStyle + + @oldStyle.setter + def oldStyle(self, value): + self.app.oldStyle = value + + @property + def parent(self): + return self.app.parent + + @parent.setter + def parent(self, value): + self.app.parent = value + + @property + def position(self): + return self.app.position + + @position.setter + def position(self, array): + """The position of the origin for the text. + + The array must contain two values. Setting this property is basically + equivalent to clicking the text tool at a point in the documents to + create the point of origin for text. + + """ + self.app.position = array + + @property + def rightIndent(self): + return self.app.rightIndent + + @rightIndent.setter + def rightIndent(self, value): + self.app.rightIndent = value + + @property + def size(self): + """The font size in UnitValue. + + NOTE: Type was points for CS3 and older. + + """ + return self.app.size + + @size.setter + def size(self, value): + self.app.size = value + + @property + def spaceAfter(self): + """The amount of space to use after each paragraph.""" + return self.app.spaceAfter + + @spaceAfter.setter + def spaceAfter(self, value): + self.app.spaceAfter = value + + @property + def spaceBefore(self): + return self.app.spaceBefore + + @spaceBefore.setter + def spaceBefore(self, value): + self.app.spaceBefore = value + + @property + def strikeThru(self): + """The text strike-through option to use.""" + return StrikeThruType(self.app.strikeThru) + + @strikeThru.setter + def strikeThru(self, value): + self.app.strikeThru = value + + @property + def textComposer(self): + return TextComposer(self.app.textComposer) + + @textComposer.setter + def textComposer(self, value): + self.app.textComposer = value + + @property + def tracking(self): + return self.app.tracking + + @tracking.setter + def tracking(self, value): + self.app.tracking = value + + @property + def underline(self): + """The text underlining options.""" + return self.app.underline + + @underline.setter + def underline(self, value): + self.app.underline = value + + @property + def useAutoLeading(self): + return self.app.useAutoLeading + + @useAutoLeading.setter + def useAutoLeading(self, value): + self.app.useAutoLeading = value + + @property + def verticalScale(self): + return self.app.verticalScale + + @verticalScale.setter + def verticalScale(self, value): + self.app.verticalScale = value + + @property + def warpBend(self): + """The warp bend percentage.""" + return self.app.warpBend + + @warpBend.setter + def warpBend(self, value): + self.app.warpBend = value + + @property + def warpDirection(self) -> Direction: + """The warp direction.""" + return Direction(self.app.warpDirection) + + @warpDirection.setter + def warpDirection(self, value): + self.app.warpDirection = value + + @property + def warpHorizontalDistortion(self): + return self.app.warpHorizontalDistortion + + @warpHorizontalDistortion.setter + def warpHorizontalDistortion(self, value): + self.app.warpHorizontalDistortion = value + + @property + def warpStyle(self): + """The style of warp to use.""" + return self.app.warpStyle + + @warpStyle.setter + def warpStyle(self, value): + self.app.warpStyle = value + + @property + def warpVerticalDistortion(self): + return self.app.warpVerticalDistortion + + @warpVerticalDistortion.setter + def warpVerticalDistortion(self, value): + self.app.warpVerticalDistortion = value + + @property + def width(self): + """The width of the bounding box for + + paragraph text. + Valid only when kind = TextType.PARAGRAPHTEXT. + + """ + return self.app.width + + @width.setter + def width(self, value: float): + """The width of the bounding box for + + paragraph text. + Valid only when kind = TextType.PARAGRAPHTEXT. + + """ + self.app.width = value + + def convertToShape(self): + """Converts the text item and its containing layer to a fill layer with the + text changed to a clipping path. + """ + return self.app.convertToShape() + + def createPath(self): + """Creates a clipping path from the outlines of the actual text items + + (such as letters or words). + + """ + return self.app.createPath() diff --git a/photoshop/session.py b/photoshop/session.py index d5976e1b..a27b66da 100644 --- a/photoshop/session.py +++ b/photoshop/session.py @@ -27,34 +27,38 @@ """ # Import built-in modules +from __future__ import annotations + from typing import Any # 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, +) # pylint: disable=too-many-arguments @@ -80,7 +84,6 @@ def __init__( ): """Session of Photoshop. - Examples: ```python @@ -297,6 +300,15 @@ def active_document(self): except errors.PhotoshopPythonAPICOMError: raise errors.PhotoshopPythonAPIError("No active document available.") + def set_active_document(self, doc: Document) -> None: + """Set active document. + + Args: + doc (Document): The document to set as active. + + """ + self._active_document = doc + @staticmethod def echo(*args, **kwargs): """Print message.""" @@ -311,19 +323,14 @@ def alert(self, text: str): """ self.app.doJavaScript(f"alert('{text}')") - @active_document.setter - def active_document(self, active_document): - """Set active document.""" - self._active_document = active_document - def _action_open(self): - self.active_document = self.app.open(self.path) + self.set_active_document(self.app.open(self.path)) def _action_new_document(self): - self.active_document = self.app.documents.add() + self.set_active_document(self.app.documents.add()) def _action_document_duplicate(self): - self.active_document = self.active_document.duplicate() + self.set_active_document(self.active_document.duplicate()) def run_action(self): try: @@ -333,7 +340,7 @@ def run_action(self): pass def close(self): - """closing current session.""" + """Closing current session.""" if self._auto_close: self.active_document.close() diff --git a/poetry.lock b/poetry.lock deleted file mode 100644 index 0a985716..00000000 --- a/poetry.lock +++ /dev/null @@ -1,1672 +0,0 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. - -[[package]] -name = "argcomplete" -version = "2.0.6" -description = "Bash tab completion for argparse" -optional = false -python-versions = ">=3.6" -files = [ - {file = "argcomplete-2.0.6-py3-none-any.whl", hash = "sha256:6c2170b3e0ab54683cb28d319b65261bde1f11388be688b68118b7d281e34c94"}, - {file = "argcomplete-2.0.6.tar.gz", hash = "sha256:dc33528d96727882b576b24bc89ed038f3c6abbb6855ff9bb6be23384afff9d6"}, -] - -[package.extras] -lint = ["flake8", "mypy"] -test = ["coverage", "flake8", "mypy", "pexpect", "wheel"] - -[[package]] -name = "astroid" -version = "2.15.8" -description = "An abstract syntax tree for Python with inference support." -optional = false -python-versions = ">=3.7.2" -files = [ - {file = "astroid-2.15.8-py3-none-any.whl", hash = "sha256:1aa149fc5c6589e3d0ece885b4491acd80af4f087baafa3fb5203b113e68cd3c"}, - {file = "astroid-2.15.8.tar.gz", hash = "sha256:6c107453dffee9055899705de3c9ead36e74119cee151e5a9aaf7f0b0e020a6a"}, -] - -[package.dependencies] -lazy-object-proxy = ">=1.4.0" -typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} -wrapt = [ - {version = ">=1.11,<2", markers = "python_version < \"3.11\""}, - {version = ">=1.14,<2", markers = "python_version >= \"3.11\""}, -] - -[[package]] -name = "babel" -version = "2.13.1" -description = "Internationalization utilities" -optional = false -python-versions = ">=3.7" -files = [ - {file = "Babel-2.13.1-py3-none-any.whl", hash = "sha256:7077a4984b02b6727ac10f1f7294484f737443d7e2e66c5e4380e41a3ae0b4ed"}, - {file = "Babel-2.13.1.tar.gz", hash = "sha256:33e0952d7dd6374af8dbf6768cc4ddf3ccfefc244f9986d4074704f2fbd18900"}, -] - -[package.dependencies] -pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""} -setuptools = {version = "*", markers = "python_version >= \"3.12\""} - -[package.extras] -dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] - -[[package]] -name = "black" -version = "23.10.1" -description = "The uncompromising code formatter." -optional = false -python-versions = ">=3.8" -files = [ - {file = "black-23.10.1-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:ec3f8e6234c4e46ff9e16d9ae96f4ef69fa328bb4ad08198c8cee45bb1f08c69"}, - {file = "black-23.10.1-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:1b917a2aa020ca600483a7b340c165970b26e9029067f019e3755b56e8dd5916"}, - {file = "black-23.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c74de4c77b849e6359c6f01987e94873c707098322b91490d24296f66d067dc"}, - {file = "black-23.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:7b4d10b0f016616a0d93d24a448100adf1699712fb7a4efd0e2c32bbb219b173"}, - {file = "black-23.10.1-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:b15b75fc53a2fbcac8a87d3e20f69874d161beef13954747e053bca7a1ce53a0"}, - {file = "black-23.10.1-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:e293e4c2f4a992b980032bbd62df07c1bcff82d6964d6c9496f2cd726e246ace"}, - {file = "black-23.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d56124b7a61d092cb52cce34182a5280e160e6aff3137172a68c2c2c4b76bcb"}, - {file = "black-23.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:3f157a8945a7b2d424da3335f7ace89c14a3b0625e6593d21139c2d8214d55ce"}, - {file = "black-23.10.1-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:cfcce6f0a384d0da692119f2d72d79ed07c7159879d0bb1bb32d2e443382bf3a"}, - {file = "black-23.10.1-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:33d40f5b06be80c1bbce17b173cda17994fbad096ce60eb22054da021bf933d1"}, - {file = "black-23.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:840015166dbdfbc47992871325799fd2dc0dcf9395e401ada6d88fe11498abad"}, - {file = "black-23.10.1-cp38-cp38-win_amd64.whl", hash = "sha256:037e9b4664cafda5f025a1728c50a9e9aedb99a759c89f760bd83730e76ba884"}, - {file = "black-23.10.1-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:7cb5936e686e782fddb1c73f8aa6f459e1ad38a6a7b0e54b403f1f05a1507ee9"}, - {file = "black-23.10.1-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:7670242e90dc129c539e9ca17665e39a146a761e681805c54fbd86015c7c84f7"}, - {file = "black-23.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ed45ac9a613fb52dad3b61c8dea2ec9510bf3108d4db88422bacc7d1ba1243d"}, - {file = "black-23.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:6d23d7822140e3fef190734216cefb262521789367fbdc0b3f22af6744058982"}, - {file = "black-23.10.1-py3-none-any.whl", hash = "sha256:d431e6739f727bb2e0495df64a6c7a5310758e87505f5f8cde9ff6c0f2d7e4fe"}, - {file = "black-23.10.1.tar.gz", hash = "sha256:1f8ce316753428ff68749c65a5f7844631aa18c8679dfd3ca9dc1a289979c258"}, -] - -[package.dependencies] -click = ">=8.0.0" -mypy-extensions = ">=0.4.3" -packaging = ">=22.0" -pathspec = ">=0.9.0" -platformdirs = ">=2" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} - -[package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] - -[[package]] -name = "certifi" -version = "2023.7.22" -description = "Python package for providing Mozilla's CA Bundle." -optional = false -python-versions = ">=3.6" -files = [ - {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, - {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, -] - -[[package]] -name = "cfgv" -version = "3.4.0" -description = "Validate configuration and produce human readable error messages." -optional = false -python-versions = ">=3.8" -files = [ - {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, - {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, -] - -[[package]] -name = "charset-normalizer" -version = "2.1.1" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -optional = false -python-versions = ">=3.6.0" -files = [ - {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, - {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, -] - -[package.extras] -unicode-backport = ["unicodedata2"] - -[[package]] -name = "click" -version = "8.1.7" -description = "Composable command line interface toolkit" -optional = false -python-versions = ">=3.7" -files = [ - {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, - {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "codecov" -version = "2.1.13" -description = "Hosted coverage reports for GitHub, Bitbucket and Gitlab" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "codecov-2.1.13-py2.py3-none-any.whl", hash = "sha256:c2ca5e51bba9ebb43644c43d0690148a55086f7f5e6fd36170858fa4206744d5"}, - {file = "codecov-2.1.13.tar.gz", hash = "sha256:2362b685633caeaf45b9951a9b76ce359cd3581dd515b430c6c3f5dfb4d92a8c"}, -] - -[package.dependencies] -coverage = "*" -requests = ">=2.7.9" - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] - -[[package]] -name = "commitizen" -version = "2.42.1" -description = "Python commitizen client tool" -optional = false -python-versions = ">=3.6.2,<4.0.0" -files = [ - {file = "commitizen-2.42.1-py3-none-any.whl", hash = "sha256:fad7d37cfae361a859b713d4ac591859d5ca03137dd52de4e1bd208f7f45d5dc"}, - {file = "commitizen-2.42.1.tar.gz", hash = "sha256:eac18c7c65587061aac6829534907aeb208405b8230bfd35ec08503c228a7f17"}, -] - -[package.dependencies] -argcomplete = ">=1.12.1,<2.1" -charset-normalizer = ">=2.1.0,<3.0.0" -colorama = ">=0.4.1,<0.5.0" -decli = ">=0.5.2,<0.6.0" -jinja2 = ">=2.10.3" -packaging = ">=19" -pyyaml = ">=3.08" -questionary = ">=1.4.0,<2.0.0" -termcolor = {version = ">=1.1,<3", markers = "python_version >= \"3.7\""} -tomlkit = ">=0.5.3,<1.0.0" -typing-extensions = ">=4.0.1,<5.0.0" - -[[package]] -name = "comtypes" -version = "1.4.8" -description = "Pure Python COM package" -optional = false -python-versions = ">=3.8" -files = [ - {file = "comtypes-1.4.8-py3-none-any.whl", hash = "sha256:773109b12aa0bec630d5b2272dd983cbaa25605a12fc1319f99730c9d0b72f79"}, - {file = "comtypes-1.4.8.zip", hash = "sha256:bb2286cfb3b96f838307200a85b00b98e1bdebf1e58ec3c28b36b1ccfafac01f"}, -] - -[[package]] -name = "coverage" -version = "7.3.2" -description = "Code coverage measurement for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "coverage-7.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d872145f3a3231a5f20fd48500274d7df222e291d90baa2026cc5152b7ce86bf"}, - {file = "coverage-7.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:310b3bb9c91ea66d59c53fa4989f57d2436e08f18fb2f421a1b0b6b8cc7fffda"}, - {file = "coverage-7.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f47d39359e2c3779c5331fc740cf4bce6d9d680a7b4b4ead97056a0ae07cb49a"}, - {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa72dbaf2c2068404b9870d93436e6d23addd8bbe9295f49cbca83f6e278179c"}, - {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:beaa5c1b4777f03fc63dfd2a6bd820f73f036bfb10e925fce067b00a340d0f3f"}, - {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dbc1b46b92186cc8074fee9d9fbb97a9dd06c6cbbef391c2f59d80eabdf0faa6"}, - {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:315a989e861031334d7bee1f9113c8770472db2ac484e5b8c3173428360a9148"}, - {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d1bc430677773397f64a5c88cb522ea43175ff16f8bfcc89d467d974cb2274f9"}, - {file = "coverage-7.3.2-cp310-cp310-win32.whl", hash = "sha256:a889ae02f43aa45032afe364c8ae84ad3c54828c2faa44f3bfcafecb5c96b02f"}, - {file = "coverage-7.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c0ba320de3fb8c6ec16e0be17ee1d3d69adcda99406c43c0409cb5c41788a611"}, - {file = "coverage-7.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ac8c802fa29843a72d32ec56d0ca792ad15a302b28ca6203389afe21f8fa062c"}, - {file = "coverage-7.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:89a937174104339e3a3ffcf9f446c00e3a806c28b1841c63edb2b369310fd074"}, - {file = "coverage-7.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e267e9e2b574a176ddb983399dec325a80dbe161f1a32715c780b5d14b5f583a"}, - {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2443cbda35df0d35dcfb9bf8f3c02c57c1d6111169e3c85fc1fcc05e0c9f39a3"}, - {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4175e10cc8dda0265653e8714b3174430b07c1dca8957f4966cbd6c2b1b8065a"}, - {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf38419fb1a347aaf63481c00f0bdc86889d9fbf3f25109cf96c26b403fda1"}, - {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5c913b556a116b8d5f6ef834038ba983834d887d82187c8f73dec21049abd65c"}, - {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1981f785239e4e39e6444c63a98da3a1db8e971cb9ceb50a945ba6296b43f312"}, - {file = "coverage-7.3.2-cp311-cp311-win32.whl", hash = "sha256:43668cabd5ca8258f5954f27a3aaf78757e6acf13c17604d89648ecc0cc66640"}, - {file = "coverage-7.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10c39c0452bf6e694511c901426d6b5ac005acc0f78ff265dbe36bf81f808a2"}, - {file = "coverage-7.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4cbae1051ab791debecc4a5dcc4a1ff45fc27b91b9aee165c8a27514dd160836"}, - {file = "coverage-7.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12d15ab5833a997716d76f2ac1e4b4d536814fc213c85ca72756c19e5a6b3d63"}, - {file = "coverage-7.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c7bba973ebee5e56fe9251300c00f1579652587a9f4a5ed8404b15a0471f216"}, - {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe494faa90ce6381770746077243231e0b83ff3f17069d748f645617cefe19d4"}, - {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6e9589bd04d0461a417562649522575d8752904d35c12907d8c9dfeba588faf"}, - {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d51ac2a26f71da1b57f2dc81d0e108b6ab177e7d30e774db90675467c847bbdf"}, - {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:99b89d9f76070237975b315b3d5f4d6956ae354a4c92ac2388a5695516e47c84"}, - {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fa28e909776dc69efb6ed975a63691bc8172b64ff357e663a1bb06ff3c9b589a"}, - {file = "coverage-7.3.2-cp312-cp312-win32.whl", hash = "sha256:289fe43bf45a575e3ab10b26d7b6f2ddb9ee2dba447499f5401cfb5ecb8196bb"}, - {file = "coverage-7.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7dbc3ed60e8659bc59b6b304b43ff9c3ed858da2839c78b804973f613d3e92ed"}, - {file = "coverage-7.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f94b734214ea6a36fe16e96a70d941af80ff3bfd716c141300d95ebc85339738"}, - {file = "coverage-7.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:af3d828d2c1cbae52d34bdbb22fcd94d1ce715d95f1a012354a75e5913f1bda2"}, - {file = "coverage-7.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:630b13e3036e13c7adc480ca42fa7afc2a5d938081d28e20903cf7fd687872e2"}, - {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9eacf273e885b02a0273bb3a2170f30e2d53a6d53b72dbe02d6701b5296101c"}, - {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8f17966e861ff97305e0801134e69db33b143bbfb36436efb9cfff6ec7b2fd9"}, - {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b4275802d16882cf9c8b3d057a0839acb07ee9379fa2749eca54efbce1535b82"}, - {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:72c0cfa5250f483181e677ebc97133ea1ab3eb68645e494775deb6a7f6f83901"}, - {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cb536f0dcd14149425996821a168f6e269d7dcd2c273a8bff8201e79f5104e76"}, - {file = "coverage-7.3.2-cp38-cp38-win32.whl", hash = "sha256:307adb8bd3abe389a471e649038a71b4eb13bfd6b7dd9a129fa856f5c695cf92"}, - {file = "coverage-7.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:88ed2c30a49ea81ea3b7f172e0269c182a44c236eb394718f976239892c0a27a"}, - {file = "coverage-7.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b631c92dfe601adf8f5ebc7fc13ced6bb6e9609b19d9a8cd59fa47c4186ad1ce"}, - {file = "coverage-7.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d3d9df4051c4a7d13036524b66ecf7a7537d14c18a384043f30a303b146164e9"}, - {file = "coverage-7.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f7363d3b6a1119ef05015959ca24a9afc0ea8a02c687fe7e2d557705375c01f"}, - {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f11cc3c967a09d3695d2a6f03fb3e6236622b93be7a4b5dc09166a861be6d25"}, - {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:149de1d2401ae4655c436a3dced6dd153f4c3309f599c3d4bd97ab172eaf02d9"}, - {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3a4006916aa6fee7cd38db3bfc95aa9c54ebb4ffbfc47c677c8bba949ceba0a6"}, - {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9028a3871280110d6e1aa2df1afd5ef003bab5fb1ef421d6dc748ae1c8ef2ebc"}, - {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9f805d62aec8eb92bab5b61c0f07329275b6f41c97d80e847b03eb894f38d083"}, - {file = "coverage-7.3.2-cp39-cp39-win32.whl", hash = "sha256:d1c88ec1a7ff4ebca0219f5b1ef863451d828cccf889c173e1253aa84b1e07ce"}, - {file = "coverage-7.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b4767da59464bb593c07afceaddea61b154136300881844768037fd5e859353f"}, - {file = "coverage-7.3.2-pp38.pp39.pp310-none-any.whl", hash = "sha256:ae97af89f0fbf373400970c0a21eef5aa941ffeed90aee43650b81f7d7f47637"}, - {file = "coverage-7.3.2.tar.gz", hash = "sha256:be32ad29341b0170e795ca590e1c07e81fc061cb5b10c74ce7203491484404ef"}, -] - -[package.dependencies] -tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} - -[package.extras] -toml = ["tomli"] - -[[package]] -name = "csscompressor" -version = "0.9.5" -description = "A python port of YUI CSS Compressor" -optional = false -python-versions = "*" -files = [ - {file = "csscompressor-0.9.5.tar.gz", hash = "sha256:afa22badbcf3120a4f392e4d22f9fff485c044a1feda4a950ecc5eba9dd31a05"}, -] - -[[package]] -name = "decli" -version = "0.5.2" -description = "Minimal, easy-to-use, declarative cli tool" -optional = false -python-versions = ">=3.6" -files = [ - {file = "decli-0.5.2-py3-none-any.whl", hash = "sha256:d3207bc02d0169bf6ed74ccca09ce62edca0eb25b0ebf8bf4ae3fb8333e15ca0"}, - {file = "decli-0.5.2.tar.gz", hash = "sha256:f2cde55034a75c819c630c7655a844c612f2598c42c21299160465df6ad463ad"}, -] - -[[package]] -name = "dill" -version = "0.3.7" -description = "serialize all of Python" -optional = false -python-versions = ">=3.7" -files = [ - {file = "dill-0.3.7-py3-none-any.whl", hash = "sha256:76b122c08ef4ce2eedcd4d1abd8e641114bfc6c2867f49f3c41facf65bf19f5e"}, - {file = "dill-0.3.7.tar.gz", hash = "sha256:cc1c8b182eb3013e24bd475ff2e9295af86c1a38eb1aff128dac8962a9ce3c03"}, -] - -[package.extras] -graph = ["objgraph (>=1.7.2)"] - -[[package]] -name = "distlib" -version = "0.3.7" -description = "Distribution utilities" -optional = false -python-versions = "*" -files = [ - {file = "distlib-0.3.7-py2.py3-none-any.whl", hash = "sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057"}, - {file = "distlib-0.3.7.tar.gz", hash = "sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8"}, -] - -[[package]] -name = "exceptiongroup" -version = "1.1.3" -description = "Backport of PEP 654 (exception groups)" -optional = false -python-versions = ">=3.7" -files = [ - {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, - {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, -] - -[package.extras] -test = ["pytest (>=6)"] - -[[package]] -name = "filelock" -version = "3.13.1" -description = "A platform independent file lock." -optional = false -python-versions = ">=3.8" -files = [ - {file = "filelock-3.13.1-py3-none-any.whl", hash = "sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c"}, - {file = "filelock-3.13.1.tar.gz", hash = "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e"}, -] - -[package.extras] -docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.24)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] -typing = ["typing-extensions (>=4.8)"] - -[[package]] -name = "flake8" -version = "4.0.1" -description = "the modular source code checker: pep8 pyflakes and co" -optional = false -python-versions = ">=3.6" -files = [ - {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, - {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"}, -] - -[package.dependencies] -mccabe = ">=0.6.0,<0.7.0" -pycodestyle = ">=2.8.0,<2.9.0" -pyflakes = ">=2.4.0,<2.5.0" - -[[package]] -name = "ghp-import" -version = "2.1.0" -description = "Copy your docs directly to the gh-pages branch." -optional = false -python-versions = "*" -files = [ - {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, - {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, -] - -[package.dependencies] -python-dateutil = ">=2.8.1" - -[package.extras] -dev = ["flake8", "markdown", "twine", "wheel"] - -[[package]] -name = "gitdb" -version = "4.0.11" -description = "Git Object Database" -optional = false -python-versions = ">=3.7" -files = [ - {file = "gitdb-4.0.11-py3-none-any.whl", hash = "sha256:81a3407ddd2ee8df444cbacea00e2d038e40150acfa3001696fe0dcf1d3adfa4"}, - {file = "gitdb-4.0.11.tar.gz", hash = "sha256:bf5421126136d6d0af55bc1e7c1af1c397a34f5b7bd79e776cd3e89785c2b04b"}, -] - -[package.dependencies] -smmap = ">=3.0.1,<6" - -[[package]] -name = "gitpython" -version = "3.1.40" -description = "GitPython is a Python library used to interact with Git repositories" -optional = false -python-versions = ">=3.7" -files = [ - {file = "GitPython-3.1.40-py3-none-any.whl", hash = "sha256:cf14627d5a8049ffbf49915732e5eddbe8134c3bdb9d476e6182b676fc573f8a"}, - {file = "GitPython-3.1.40.tar.gz", hash = "sha256:22b126e9ffb671fdd0c129796343a02bf67bf2994b35449ffc9321aa755e18a4"}, -] - -[package.dependencies] -gitdb = ">=4.0.1,<5" - -[package.extras] -test = ["black", "coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", "pytest", "pytest-cov", "pytest-instafail", "pytest-subtests", "pytest-sugar"] - -[[package]] -name = "griffe" -version = "0.38.0" -description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." -optional = false -python-versions = ">=3.8" -files = [ - {file = "griffe-0.38.0-py3-none-any.whl", hash = "sha256:6a5bc457320e8e199006aa5fbb03e162f5e21abe31aa6221f7a5c37ea0724c71"}, - {file = "griffe-0.38.0.tar.gz", hash = "sha256:9b97487b583042b543d1e28196caee638ecd766c8c4c98135071806cb5333ac2"}, -] - -[package.dependencies] -colorama = ">=0.4" - -[[package]] -name = "htmlmin" -version = "0.1.12" -description = "An HTML Minifier" -optional = false -python-versions = "*" -files = [ - {file = "htmlmin-0.1.12.tar.gz", hash = "sha256:50c1ef4630374a5d723900096a961cff426dff46b48f34d194a81bbe14eca178"}, -] - -[[package]] -name = "identify" -version = "2.5.31" -description = "File identification library for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "identify-2.5.31-py2.py3-none-any.whl", hash = "sha256:90199cb9e7bd3c5407a9b7e81b4abec4bb9d249991c79439ec8af740afc6293d"}, - {file = "identify-2.5.31.tar.gz", hash = "sha256:7736b3c7a28233637e3c36550646fc6389bedd74ae84cb788200cc8e2dd60b75"}, -] - -[package.extras] -license = ["ukkonen"] - -[[package]] -name = "idna" -version = "3.4" -description = "Internationalized Domain Names in Applications (IDNA)" -optional = false -python-versions = ">=3.5" -files = [ - {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, - {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, -] - -[[package]] -name = "importlib-metadata" -version = "6.8.0" -description = "Read metadata from Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "importlib_metadata-6.8.0-py3-none-any.whl", hash = "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb"}, - {file = "importlib_metadata-6.8.0.tar.gz", hash = "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743"}, -] - -[package.dependencies] -zipp = ">=0.5" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] - -[[package]] -name = "iniconfig" -version = "2.0.0" -description = "brain-dead simple config-ini parsing" -optional = false -python-versions = ">=3.7" -files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, -] - -[[package]] -name = "isort" -version = "5.13.2" -description = "A Python utility / library to sort Python imports." -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, - {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, -] - -[package.extras] -colors = ["colorama (>=0.4.6)"] - -[[package]] -name = "jinja2" -version = "3.1.2" -description = "A very fast and expressive template engine." -optional = false -python-versions = ">=3.7" -files = [ - {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, - {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, -] - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "jsmin" -version = "3.0.1" -description = "JavaScript minifier." -optional = false -python-versions = "*" -files = [ - {file = "jsmin-3.0.1.tar.gz", hash = "sha256:c0959a121ef94542e807a674142606f7e90214a2b3d1eb17300244bbb5cc2bfc"}, -] - -[[package]] -name = "lazy-object-proxy" -version = "1.9.0" -description = "A fast and thorough lazy object proxy." -optional = false -python-versions = ">=3.7" -files = [ - {file = "lazy-object-proxy-1.9.0.tar.gz", hash = "sha256:659fb5809fa4629b8a1ac5106f669cfc7bef26fbb389dda53b3e010d1ac4ebae"}, - {file = "lazy_object_proxy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b40387277b0ed2d0602b8293b94d7257e17d1479e257b4de114ea11a8cb7f2d7"}, - {file = "lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8c6cfb338b133fbdbc5cfaa10fe3c6aeea827db80c978dbd13bc9dd8526b7d4"}, - {file = "lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:721532711daa7db0d8b779b0bb0318fa87af1c10d7fe5e52ef30f8eff254d0cd"}, - {file = "lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:66a3de4a3ec06cd8af3f61b8e1ec67614fbb7c995d02fa224813cb7afefee701"}, - {file = "lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1aa3de4088c89a1b69f8ec0dcc169aa725b0ff017899ac568fe44ddc1396df46"}, - {file = "lazy_object_proxy-1.9.0-cp310-cp310-win32.whl", hash = "sha256:f0705c376533ed2a9e5e97aacdbfe04cecd71e0aa84c7c0595d02ef93b6e4455"}, - {file = "lazy_object_proxy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:ea806fd4c37bf7e7ad82537b0757999264d5f70c45468447bb2b91afdbe73a6e"}, - {file = "lazy_object_proxy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:946d27deaff6cf8452ed0dba83ba38839a87f4f7a9732e8f9fd4107b21e6ff07"}, - {file = "lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79a31b086e7e68b24b99b23d57723ef7e2c6d81ed21007b6281ebcd1688acb0a"}, - {file = "lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f699ac1c768270c9e384e4cbd268d6e67aebcfae6cd623b4d7c3bfde5a35db59"}, - {file = "lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bfb38f9ffb53b942f2b5954e0f610f1e721ccebe9cce9025a38c8ccf4a5183a4"}, - {file = "lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:189bbd5d41ae7a498397287c408617fe5c48633e7755287b21d741f7db2706a9"}, - {file = "lazy_object_proxy-1.9.0-cp311-cp311-win32.whl", hash = "sha256:81fc4d08b062b535d95c9ea70dbe8a335c45c04029878e62d744bdced5141586"}, - {file = "lazy_object_proxy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:f2457189d8257dd41ae9b434ba33298aec198e30adf2dcdaaa3a28b9994f6adb"}, - {file = "lazy_object_proxy-1.9.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d9e25ef10a39e8afe59a5c348a4dbf29b4868ab76269f81ce1674494e2565a6e"}, - {file = "lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cbf9b082426036e19c6924a9ce90c740a9861e2bdc27a4834fd0a910742ac1e8"}, - {file = "lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f5fa4a61ce2438267163891961cfd5e32ec97a2c444e5b842d574251ade27d2"}, - {file = "lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8fa02eaab317b1e9e03f69aab1f91e120e7899b392c4fc19807a8278a07a97e8"}, - {file = "lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e7c21c95cae3c05c14aafffe2865bbd5e377cfc1348c4f7751d9dc9a48ca4bda"}, - {file = "lazy_object_proxy-1.9.0-cp37-cp37m-win32.whl", hash = "sha256:f12ad7126ae0c98d601a7ee504c1122bcef553d1d5e0c3bfa77b16b3968d2734"}, - {file = "lazy_object_proxy-1.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:edd20c5a55acb67c7ed471fa2b5fb66cb17f61430b7a6b9c3b4a1e40293b1671"}, - {file = "lazy_object_proxy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d0daa332786cf3bb49e10dc6a17a52f6a8f9601b4cf5c295a4f85854d61de63"}, - {file = "lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cd077f3d04a58e83d04b20e334f678c2b0ff9879b9375ed107d5d07ff160171"}, - {file = "lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:660c94ea760b3ce47d1855a30984c78327500493d396eac4dfd8bd82041b22be"}, - {file = "lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:212774e4dfa851e74d393a2370871e174d7ff0ebc980907723bb67d25c8a7c30"}, - {file = "lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f0117049dd1d5635bbff65444496c90e0baa48ea405125c088e93d9cf4525b11"}, - {file = "lazy_object_proxy-1.9.0-cp38-cp38-win32.whl", hash = "sha256:0a891e4e41b54fd5b8313b96399f8b0e173bbbfc03c7631f01efbe29bb0bcf82"}, - {file = "lazy_object_proxy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:9990d8e71b9f6488e91ad25f322898c136b008d87bf852ff65391b004da5e17b"}, - {file = "lazy_object_proxy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9e7551208b2aded9c1447453ee366f1c4070602b3d932ace044715d89666899b"}, - {file = "lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f83ac4d83ef0ab017683d715ed356e30dd48a93746309c8f3517e1287523ef4"}, - {file = "lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7322c3d6f1766d4ef1e51a465f47955f1e8123caee67dd641e67d539a534d006"}, - {file = "lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:18b78ec83edbbeb69efdc0e9c1cb41a3b1b1ed11ddd8ded602464c3fc6020494"}, - {file = "lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:09763491ce220c0299688940f8dc2c5d05fd1f45af1e42e636b2e8b2303e4382"}, - {file = "lazy_object_proxy-1.9.0-cp39-cp39-win32.whl", hash = "sha256:9090d8e53235aa280fc9239a86ae3ea8ac58eff66a705fa6aa2ec4968b95c821"}, - {file = "lazy_object_proxy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:db1c1722726f47e10e0b5fdbf15ac3b8adb58c091d12b3ab713965795036985f"}, -] - -[[package]] -name = "markdown" -version = "3.5.1" -description = "Python implementation of John Gruber's Markdown." -optional = false -python-versions = ">=3.8" -files = [ - {file = "Markdown-3.5.1-py3-none-any.whl", hash = "sha256:5874b47d4ee3f0b14d764324d2c94c03ea66bee56f2d929da9f2508d65e722dc"}, - {file = "Markdown-3.5.1.tar.gz", hash = "sha256:b65d7beb248dc22f2e8a31fb706d93798093c308dc1aba295aedeb9d41a813bd"}, -] - -[package.dependencies] -importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} - -[package.extras] -docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.5)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"] -testing = ["coverage", "pyyaml"] - -[[package]] -name = "markupsafe" -version = "2.1.3" -description = "Safely add untrusted strings to HTML/XML markup." -optional = false -python-versions = ">=3.7" -files = [ - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, - {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, -] - -[[package]] -name = "mccabe" -version = "0.6.1" -description = "McCabe checker, plugin for flake8" -optional = false -python-versions = "*" -files = [ - {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, - {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, -] - -[[package]] -name = "mergedeep" -version = "1.3.4" -description = "A deep merge function for 🐍." -optional = false -python-versions = ">=3.6" -files = [ - {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, - {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, -] - -[[package]] -name = "mkdocs" -version = "1.6.1" -description = "Project documentation with Markdown." -optional = false -python-versions = ">=3.8" -files = [ - {file = "mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e"}, - {file = "mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2"}, -] - -[package.dependencies] -click = ">=7.0" -colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""} -ghp-import = ">=1.0" -importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} -jinja2 = ">=2.11.1" -markdown = ">=3.3.6" -markupsafe = ">=2.0.1" -mergedeep = ">=1.3.4" -mkdocs-get-deps = ">=0.2.0" -packaging = ">=20.5" -pathspec = ">=0.11.1" -pyyaml = ">=5.1" -pyyaml-env-tag = ">=0.1" -watchdog = ">=2.0" - -[package.extras] -i18n = ["babel (>=2.9.0)"] -min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.4)", "jinja2 (==2.11.1)", "markdown (==3.3.6)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "mkdocs-get-deps (==0.2.0)", "packaging (==20.5)", "pathspec (==0.11.1)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "watchdog (==2.0)"] - -[[package]] -name = "mkdocs-autolinks-plugin" -version = "0.7.1" -description = "An MkDocs plugin" -optional = false -python-versions = ">=3.4" -files = [ - {file = "mkdocs-autolinks-plugin-0.7.1.tar.gz", hash = "sha256:445ddb9b417b7795856c30801bb430773186c1daf210bdeecf8305f55a47d151"}, - {file = "mkdocs_autolinks_plugin-0.7.1-py3-none-any.whl", hash = "sha256:5c6c17f6649b68e79a9ef0b2648d59f3072e18002b90ee1586a64c505f11ab12"}, -] - -[package.dependencies] -mkdocs = ">=1.2.3" - -[[package]] -name = "mkdocs-autorefs" -version = "0.5.0" -description = "Automatically link across pages in MkDocs." -optional = false -python-versions = ">=3.8" -files = [ - {file = "mkdocs_autorefs-0.5.0-py3-none-any.whl", hash = "sha256:7930fcb8ac1249f10e683967aeaddc0af49d90702af111a5e390e8b20b3d97ff"}, - {file = "mkdocs_autorefs-0.5.0.tar.gz", hash = "sha256:9a5054a94c08d28855cfab967ada10ed5be76e2bfad642302a610b252c3274c0"}, -] - -[package.dependencies] -Markdown = ">=3.3" -mkdocs = ">=1.1" - -[[package]] -name = "mkdocs-gen-files" -version = "0.5.0" -description = "MkDocs plugin to programmatically generate documentation pages during the build" -optional = false -python-versions = ">=3.7" -files = [ - {file = "mkdocs_gen_files-0.5.0-py3-none-any.whl", hash = "sha256:7ac060096f3f40bd19039e7277dd3050be9a453c8ac578645844d4d91d7978ea"}, - {file = "mkdocs_gen_files-0.5.0.tar.gz", hash = "sha256:4c7cf256b5d67062a788f6b1d035e157fc1a9498c2399be9af5257d4ff4d19bc"}, -] - -[package.dependencies] -mkdocs = ">=1.0.3" - -[[package]] -name = "mkdocs-get-deps" -version = "0.2.0" -description = "MkDocs extension that lists all dependencies according to a mkdocs.yml file" -optional = false -python-versions = ">=3.8" -files = [ - {file = "mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134"}, - {file = "mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c"}, -] - -[package.dependencies] -importlib-metadata = {version = ">=4.3", markers = "python_version < \"3.10\""} -mergedeep = ">=1.3.4" -platformdirs = ">=2.2.0" -pyyaml = ">=5.1" - -[[package]] -name = "mkdocs-git-revision-date-localized-plugin" -version = "1.2.1" -description = "Mkdocs plugin that enables displaying the localized date of the last git modification of a markdown file." -optional = false -python-versions = ">=3.6" -files = [ - {file = "mkdocs-git-revision-date-localized-plugin-1.2.1.tar.gz", hash = "sha256:fc5b23a9d572cbba0114e9e17152001d01724990cb308830e58291fa614faf73"}, - {file = "mkdocs_git_revision_date_localized_plugin-1.2.1-py3-none-any.whl", hash = "sha256:d57dc99d67af917899e69c392f1ebccd1779fa243d641255469b03f8a3596b96"}, -] - -[package.dependencies] -babel = ">=2.7.0" -GitPython = "*" -mkdocs = ">=1.0" -pytz = "*" - -[[package]] -name = "mkdocs-git-revision-date-plugin" -version = "0.3.2" -description = "MkDocs plugin for setting revision date from git per markdown file." -optional = false -python-versions = ">=3.4" -files = [ - {file = "mkdocs_git_revision_date_plugin-0.3.2-py3-none-any.whl", hash = "sha256:2e67956cb01823dd2418e2833f3623dee8604cdf223bddd005fe36226a56f6ef"}, -] - -[package.dependencies] -GitPython = "*" -jinja2 = "*" -mkdocs = ">=0.17" - -[[package]] -name = "mkdocs-include-markdown-plugin" -version = "3.9.1" -description = "Mkdocs Markdown includer plugin." -optional = false -python-versions = ">=3.6" -files = [ - {file = "mkdocs_include_markdown_plugin-3.9.1-py3-none-any.whl", hash = "sha256:f33687e29ac66d045ba181ea50f054646b0090b42b0a4318f08e7f1d1235e6f6"}, - {file = "mkdocs_include_markdown_plugin-3.9.1.tar.gz", hash = "sha256:5e5698e78d7fea111be9873a456089daa333497988405acaac8eba2924a19152"}, -] - -[package.extras] -dev = ["bump2version (==1.0.1)", "mkdocs (==1.4.0)", "pre-commit", "pytest (==7.1.3)", "pytest-cov (==3.0.0)", "tox"] -test = ["mkdocs (==1.4.0)", "pytest (==7.1.3)", "pytest-cov (==3.0.0)"] - -[[package]] -name = "mkdocs-literate-nav" -version = "0.6.1" -description = "MkDocs plugin to specify the navigation in Markdown instead of YAML" -optional = false -python-versions = ">=3.7" -files = [ - {file = "mkdocs_literate_nav-0.6.1-py3-none-any.whl", hash = "sha256:e70bdc4a07050d32da79c0b697bd88e9a104cf3294282e9cb20eec94c6b0f401"}, - {file = "mkdocs_literate_nav-0.6.1.tar.gz", hash = "sha256:78a7ab6d878371728acb0cdc6235c9b0ffc6e83c997b037f4a5c6ff7cef7d759"}, -] - -[package.dependencies] -mkdocs = ">=1.0.3" - -[[package]] -name = "mkdocs-material" -version = "8.5.11" -description = "Documentation that simply works" -optional = false -python-versions = ">=3.7" -files = [ - {file = "mkdocs_material-8.5.11-py3-none-any.whl", hash = "sha256:c907b4b052240a5778074a30a78f31a1f8ff82d7012356dc26898b97559f082e"}, - {file = "mkdocs_material-8.5.11.tar.gz", hash = "sha256:b0ea0513fd8cab323e8a825d6692ea07fa83e917bb5db042e523afecc7064ab7"}, -] - -[package.dependencies] -jinja2 = ">=3.0.2" -markdown = ">=3.2" -mkdocs = ">=1.4.0" -mkdocs-material-extensions = ">=1.1" -pygments = ">=2.12" -pymdown-extensions = ">=9.4" -requests = ">=2.26" - -[[package]] -name = "mkdocs-material-extensions" -version = "1.3" -description = "Extension pack for Python Markdown and MkDocs Material." -optional = false -python-versions = ">=3.8" -files = [ - {file = "mkdocs_material_extensions-1.3-py3-none-any.whl", hash = "sha256:0297cc48ba68a9fdd1ef3780a3b41b534b0d0df1d1181a44676fda5f464eeadc"}, - {file = "mkdocs_material_extensions-1.3.tar.gz", hash = "sha256:f0446091503acb110a7cab9349cbc90eeac51b58d1caa92a704a81ca1e24ddbd"}, -] - -[[package]] -name = "mkdocs-minify-plugin" -version = "0.5.0" -description = "An MkDocs plugin to minify HTML, JS or CSS files prior to being written to disk" -optional = false -python-versions = ">=3.0" -files = [ - {file = "mkdocs-minify-plugin-0.5.0.tar.gz", hash = "sha256:32d9e8fbd89327a0f4f648f517297aad344c1bad64cfde110d059bd2f2780a6d"}, - {file = "mkdocs_minify_plugin-0.5.0-py2-none-any.whl", hash = "sha256:487c31ae6b8b3230f56910ce6bcf5c7e6ad9a8c4f51c720a4b989f30c2b0233f"}, -] - -[package.dependencies] -csscompressor = ">=0.9.5" -htmlmin = ">=0.1.4" -jsmin = ">=3.0.0" -mkdocs = ">=1.0.4" - -[[package]] -name = "mkdocs-pymdownx-material-extras" -version = "2.6" -description = "Plugin to extend MkDocs Material theme." -optional = false -python-versions = ">=3.8" -files = [ - {file = "mkdocs_pymdownx_material_extras-2.6-py3-none-any.whl", hash = "sha256:9a005c933c70fdfd2bdb320022b23ddce0b3fc5595aeef7084c8b7613804190b"}, - {file = "mkdocs_pymdownx_material_extras-2.6.tar.gz", hash = "sha256:2aae99cd91a604811ec8b750cc9526f8a404961d90cee30fd43ff112f651d8b1"}, -] - -[package.dependencies] -mkdocs-material = ">=8.3.3" - -[[package]] -name = "mkdocs-same-dir" -version = "0.1.3" -description = "MkDocs plugin to allow placing mkdocs.yml in the same directory as documentation" -optional = false -python-versions = ">=3.8" -files = [ - {file = "mkdocs_same_dir-0.1.3-py3-none-any.whl", hash = "sha256:3d094649e2e47efcf90a8b0051a4c2b837aaf4137a28c8e334ba9465804a317e"}, - {file = "mkdocs_same_dir-0.1.3.tar.gz", hash = "sha256:c849556b1d79ae270947f41bb89d442aa1e858ab6ec6423eb178ae76a7f984fc"}, -] - -[package.dependencies] -mkdocs = ">=1.0.3" - -[[package]] -name = "mkdocstrings" -version = "0.23.0" -description = "Automatic documentation from sources, for MkDocs." -optional = false -python-versions = ">=3.8" -files = [ - {file = "mkdocstrings-0.23.0-py3-none-any.whl", hash = "sha256:051fa4014dfcd9ed90254ae91de2dbb4f24e166347dae7be9a997fe16316c65e"}, - {file = "mkdocstrings-0.23.0.tar.gz", hash = "sha256:d9c6a37ffbe7c14a7a54ef1258c70b8d394e6a33a1c80832bce40b9567138d1c"}, -] - -[package.dependencies] -importlib-metadata = {version = ">=4.6", markers = "python_version < \"3.10\""} -Jinja2 = ">=2.11.1" -Markdown = ">=3.3" -MarkupSafe = ">=1.1" -mkdocs = ">=1.2" -mkdocs-autorefs = ">=0.3.1" -pymdown-extensions = ">=6.3" -typing-extensions = {version = ">=4.1", markers = "python_version < \"3.10\""} - -[package.extras] -crystal = ["mkdocstrings-crystal (>=0.3.4)"] -python = ["mkdocstrings-python (>=0.5.2)"] -python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"] - -[[package]] -name = "mkdocstrings-python" -version = "1.7.5" -description = "A Python handler for mkdocstrings." -optional = false -python-versions = ">=3.8" -files = [ - {file = "mkdocstrings_python-1.7.5-py3-none-any.whl", hash = "sha256:5f6246026353f0c0785135db70c3fe9a5d9318990fc7ceb11d62097b8ffdd704"}, - {file = "mkdocstrings_python-1.7.5.tar.gz", hash = "sha256:c7d143728257dbf1aa550446555a554b760dcd40a763f077189d298502b800be"}, -] - -[package.dependencies] -griffe = ">=0.37" -mkdocstrings = ">=0.20" - -[[package]] -name = "mypy" -version = "1.11.2" -description = "Optional static typing for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "mypy-1.11.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d42a6dd818ffce7be66cce644f1dff482f1d97c53ca70908dff0b9ddc120b77a"}, - {file = "mypy-1.11.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:801780c56d1cdb896eacd5619a83e427ce436d86a3bdf9112527f24a66618fef"}, - {file = "mypy-1.11.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41ea707d036a5307ac674ea172875f40c9d55c5394f888b168033177fce47383"}, - {file = "mypy-1.11.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6e658bd2d20565ea86da7d91331b0eed6d2eee22dc031579e6297f3e12c758c8"}, - {file = "mypy-1.11.2-cp310-cp310-win_amd64.whl", hash = "sha256:478db5f5036817fe45adb7332d927daa62417159d49783041338921dcf646fc7"}, - {file = "mypy-1.11.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75746e06d5fa1e91bfd5432448d00d34593b52e7e91a187d981d08d1f33d4385"}, - {file = "mypy-1.11.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a976775ab2256aadc6add633d44f100a2517d2388906ec4f13231fafbb0eccca"}, - {file = "mypy-1.11.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd953f221ac1379050a8a646585a29574488974f79d8082cedef62744f0a0104"}, - {file = "mypy-1.11.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:57555a7715c0a34421013144a33d280e73c08df70f3a18a552938587ce9274f4"}, - {file = "mypy-1.11.2-cp311-cp311-win_amd64.whl", hash = "sha256:36383a4fcbad95f2657642a07ba22ff797de26277158f1cc7bd234821468b1b6"}, - {file = "mypy-1.11.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e8960dbbbf36906c5c0b7f4fbf2f0c7ffb20f4898e6a879fcf56a41a08b0d318"}, - {file = "mypy-1.11.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:06d26c277962f3fb50e13044674aa10553981ae514288cb7d0a738f495550b36"}, - {file = "mypy-1.11.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e7184632d89d677973a14d00ae4d03214c8bc301ceefcdaf5c474866814c987"}, - {file = "mypy-1.11.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3a66169b92452f72117e2da3a576087025449018afc2d8e9bfe5ffab865709ca"}, - {file = "mypy-1.11.2-cp312-cp312-win_amd64.whl", hash = "sha256:969ea3ef09617aff826885a22ece0ddef69d95852cdad2f60c8bb06bf1f71f70"}, - {file = "mypy-1.11.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:37c7fa6121c1cdfcaac97ce3d3b5588e847aa79b580c1e922bb5d5d2902df19b"}, - {file = "mypy-1.11.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4a8a53bc3ffbd161b5b2a4fff2f0f1e23a33b0168f1c0778ec70e1a3d66deb86"}, - {file = "mypy-1.11.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ff93107f01968ed834f4256bc1fc4475e2fecf6c661260066a985b52741ddce"}, - {file = "mypy-1.11.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:edb91dded4df17eae4537668b23f0ff6baf3707683734b6a818d5b9d0c0c31a1"}, - {file = "mypy-1.11.2-cp38-cp38-win_amd64.whl", hash = "sha256:ee23de8530d99b6db0573c4ef4bd8f39a2a6f9b60655bf7a1357e585a3486f2b"}, - {file = "mypy-1.11.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:801ca29f43d5acce85f8e999b1e431fb479cb02d0e11deb7d2abb56bdaf24fd6"}, - {file = "mypy-1.11.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:af8d155170fcf87a2afb55b35dc1a0ac21df4431e7d96717621962e4b9192e70"}, - {file = "mypy-1.11.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7821776e5c4286b6a13138cc935e2e9b6fde05e081bdebf5cdb2bb97c9df81d"}, - {file = "mypy-1.11.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:539c570477a96a4e6fb718b8d5c3e0c0eba1f485df13f86d2970c91f0673148d"}, - {file = "mypy-1.11.2-cp39-cp39-win_amd64.whl", hash = "sha256:3f14cd3d386ac4d05c5a39a51b84387403dadbd936e17cb35882134d4f8f0d24"}, - {file = "mypy-1.11.2-py3-none-any.whl", hash = "sha256:b499bc07dbdcd3de92b0a8b29fdf592c111276f6a12fe29c30f6c417dd546d12"}, - {file = "mypy-1.11.2.tar.gz", hash = "sha256:7f9993ad3e0ffdc95c2a14b66dee63729f021968bff8ad911867579c65d13a79"}, -] - -[package.dependencies] -mypy-extensions = ">=1.0.0" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = ">=4.6.0" - -[package.extras] -dmypy = ["psutil (>=4.0)"] -install-types = ["pip"] -mypyc = ["setuptools (>=50)"] -reports = ["lxml"] - -[[package]] -name = "mypy-extensions" -version = "1.0.0" -description = "Type system extensions for programs checked with the mypy type checker." -optional = false -python-versions = ">=3.5" -files = [ - {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, - {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, -] - -[[package]] -name = "nodeenv" -version = "1.8.0" -description = "Node.js virtual environment builder" -optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" -files = [ - {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, - {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, -] - -[package.dependencies] -setuptools = "*" - -[[package]] -name = "packaging" -version = "23.2" -description = "Core utilities for Python packages" -optional = false -python-versions = ">=3.7" -files = [ - {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, - {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, -] - -[[package]] -name = "pathspec" -version = "0.11.2" -description = "Utility library for gitignore style pattern matching of file paths." -optional = false -python-versions = ">=3.7" -files = [ - {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, - {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, -] - -[[package]] -name = "platformdirs" -version = "3.11.0" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -optional = false -python-versions = ">=3.7" -files = [ - {file = "platformdirs-3.11.0-py3-none-any.whl", hash = "sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e"}, - {file = "platformdirs-3.11.0.tar.gz", hash = "sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3"}, -] - -[package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] - -[[package]] -name = "pluggy" -version = "1.3.0" -description = "plugin and hook calling mechanisms for python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, - {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, -] - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] - -[[package]] -name = "pre-commit" -version = "2.21.0" -description = "A framework for managing and maintaining multi-language pre-commit hooks." -optional = false -python-versions = ">=3.7" -files = [ - {file = "pre_commit-2.21.0-py2.py3-none-any.whl", hash = "sha256:e2f91727039fc39a92f58a588a25b87f936de6567eed4f0e673e0507edc75bad"}, - {file = "pre_commit-2.21.0.tar.gz", hash = "sha256:31ef31af7e474a8d8995027fefdfcf509b5c913ff31f2015b4ec4beb26a6f658"}, -] - -[package.dependencies] -cfgv = ">=2.0.0" -identify = ">=1.0.0" -nodeenv = ">=0.11.1" -pyyaml = ">=5.1" -virtualenv = ">=20.10.0" - -[[package]] -name = "prompt-toolkit" -version = "3.0.39" -description = "Library for building powerful interactive command lines in Python" -optional = false -python-versions = ">=3.7.0" -files = [ - {file = "prompt_toolkit-3.0.39-py3-none-any.whl", hash = "sha256:9dffbe1d8acf91e3de75f3b544e4842382fc06c6babe903ac9acb74dc6e08d88"}, - {file = "prompt_toolkit-3.0.39.tar.gz", hash = "sha256:04505ade687dc26dc4284b1ad19a83be2f2afe83e7a828ace0c72f3a1df72aac"}, -] - -[package.dependencies] -wcwidth = "*" - -[[package]] -name = "pycodestyle" -version = "2.8.0" -description = "Python style guide checker" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -files = [ - {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"}, - {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, -] - -[[package]] -name = "pyflakes" -version = "2.4.0" -description = "passive checker of Python programs" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"}, - {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, -] - -[[package]] -name = "pygments" -version = "2.16.1" -description = "Pygments is a syntax highlighting package written in Python." -optional = false -python-versions = ">=3.7" -files = [ - {file = "Pygments-2.16.1-py3-none-any.whl", hash = "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692"}, - {file = "Pygments-2.16.1.tar.gz", hash = "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"}, -] - -[package.extras] -plugins = ["importlib-metadata"] - -[[package]] -name = "pylint" -version = "2.17.7" -description = "python code static checker" -optional = false -python-versions = ">=3.7.2" -files = [ - {file = "pylint-2.17.7-py3-none-any.whl", hash = "sha256:27a8d4c7ddc8c2f8c18aa0050148f89ffc09838142193fdbe98f172781a3ff87"}, - {file = "pylint-2.17.7.tar.gz", hash = "sha256:f4fcac7ae74cfe36bc8451e931d8438e4a476c20314b1101c458ad0f05191fad"}, -] - -[package.dependencies] -astroid = ">=2.15.8,<=2.17.0-dev0" -colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} -dill = [ - {version = ">=0.2", markers = "python_version < \"3.11\""}, - {version = ">=0.3.6", markers = "python_version >= \"3.11\""}, -] -isort = ">=4.2.5,<6" -mccabe = ">=0.6,<0.8" -platformdirs = ">=2.2.0" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -tomlkit = ">=0.10.1" -typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} - -[package.extras] -spelling = ["pyenchant (>=3.2,<4.0)"] -testutils = ["gitpython (>3)"] - -[[package]] -name = "pymdown-extensions" -version = "10.3.1" -description = "Extension pack for Python Markdown." -optional = false -python-versions = ">=3.8" -files = [ - {file = "pymdown_extensions-10.3.1-py3-none-any.whl", hash = "sha256:8cba67beb2a1318cdaf742d09dff7c0fc4cafcc290147ade0f8fb7b71522711a"}, - {file = "pymdown_extensions-10.3.1.tar.gz", hash = "sha256:f6c79941498a458852853872e379e7bab63888361ba20992fc8b4f8a9b61735e"}, -] - -[package.dependencies] -markdown = ">=3.2" -pyyaml = "*" - -[package.extras] -extra = ["pygments (>=2.12)"] - -[[package]] -name = "pytest" -version = "7.4.3" -description = "pytest: simple powerful testing with Python" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"}, - {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=0.12,<2.0" -tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} - -[package.extras] -testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] - -[[package]] -name = "pytest-cov" -version = "4.1.0" -description = "Pytest plugin for measuring coverage." -optional = false -python-versions = ">=3.7" -files = [ - {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, - {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, -] - -[package.dependencies] -coverage = {version = ">=5.2.1", extras = ["toml"]} -pytest = ">=4.6" - -[package.extras] -testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] - -[[package]] -name = "python-dateutil" -version = "2.8.2" -description = "Extensions to the standard Python datetime module" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -files = [ - {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, - {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, -] - -[package.dependencies] -six = ">=1.5" - -[[package]] -name = "pytz" -version = "2023.3.post1" -description = "World timezone definitions, modern and historical" -optional = false -python-versions = "*" -files = [ - {file = "pytz-2023.3.post1-py2.py3-none-any.whl", hash = "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7"}, - {file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"}, -] - -[[package]] -name = "pyyaml" -version = "6.0.1" -description = "YAML parser and emitter for Python" -optional = false -python-versions = ">=3.6" -files = [ - {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, - {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, - {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, - {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, - {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, - {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, - {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, - {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, - {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, - {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, - {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, - {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, - {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, - {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, -] - -[[package]] -name = "pyyaml-env-tag" -version = "0.1" -description = "A custom YAML tag for referencing environment variables in YAML files. " -optional = false -python-versions = ">=3.6" -files = [ - {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"}, - {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"}, -] - -[package.dependencies] -pyyaml = "*" - -[[package]] -name = "questionary" -version = "1.10.0" -description = "Python library to build pretty command line user prompts ⭐️" -optional = false -python-versions = ">=3.6,<4.0" -files = [ - {file = "questionary-1.10.0-py3-none-any.whl", hash = "sha256:fecfcc8cca110fda9d561cb83f1e97ecbb93c613ff857f655818839dac74ce90"}, - {file = "questionary-1.10.0.tar.gz", hash = "sha256:600d3aefecce26d48d97eee936fdb66e4bc27f934c3ab6dd1e292c4f43946d90"}, -] - -[package.dependencies] -prompt_toolkit = ">=2.0,<4.0" - -[package.extras] -docs = ["Sphinx (>=3.3,<4.0)", "sphinx-autobuild (>=2020.9.1,<2021.0.0)", "sphinx-autodoc-typehints (>=1.11.1,<2.0.0)", "sphinx-copybutton (>=0.3.1,<0.4.0)", "sphinx-rtd-theme (>=0.5.0,<0.6.0)"] - -[[package]] -name = "requests" -version = "2.31.0" -description = "Python HTTP for Humans." -optional = false -python-versions = ">=3.7" -files = [ - {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, - {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, -] - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<3" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -name = "setuptools" -version = "68.2.2" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "setuptools-68.2.2-py3-none-any.whl", hash = "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a"}, - {file = "setuptools-68.2.2.tar.gz", hash = "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87"}, -] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] - -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] - -[[package]] -name = "smmap" -version = "5.0.1" -description = "A pure Python implementation of a sliding window memory map manager" -optional = false -python-versions = ">=3.7" -files = [ - {file = "smmap-5.0.1-py3-none-any.whl", hash = "sha256:e6d8668fa5f93e706934a62d7b4db19c8d9eb8cf2adbb75ef1b675aa332b69da"}, - {file = "smmap-5.0.1.tar.gz", hash = "sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62"}, -] - -[[package]] -name = "stringcase" -version = "1.2.0" -description = "String case converter." -optional = false -python-versions = "*" -files = [ - {file = "stringcase-1.2.0.tar.gz", hash = "sha256:48a06980661908efe8d9d34eab2b6c13aefa2163b3ced26972902e3bdfd87008"}, -] - -[[package]] -name = "termcolor" -version = "2.3.0" -description = "ANSI color formatting for output in terminal" -optional = false -python-versions = ">=3.7" -files = [ - {file = "termcolor-2.3.0-py3-none-any.whl", hash = "sha256:3afb05607b89aed0ffe25202399ee0867ad4d3cb4180d98aaf8eefa6a5f7d475"}, - {file = "termcolor-2.3.0.tar.gz", hash = "sha256:b5b08f68937f138fe92f6c089b99f1e2da0ae56c52b78bf7075fd95420fd9a5a"}, -] - -[package.extras] -tests = ["pytest", "pytest-cov"] - -[[package]] -name = "tomli" -version = "2.0.1" -description = "A lil' TOML parser" -optional = false -python-versions = ">=3.7" -files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, -] - -[[package]] -name = "tomlkit" -version = "0.12.2" -description = "Style preserving TOML library" -optional = false -python-versions = ">=3.7" -files = [ - {file = "tomlkit-0.12.2-py3-none-any.whl", hash = "sha256:eeea7ac7563faeab0a1ed8fe12c2e5a51c61f933f2502f7e9db0241a65163ad0"}, - {file = "tomlkit-0.12.2.tar.gz", hash = "sha256:df32fab589a81f0d7dc525a4267b6d7a64ee99619cbd1eeb0fae32c1dd426977"}, -] - -[[package]] -name = "typing-extensions" -version = "4.8.0" -description = "Backported and Experimental Type Hints for Python 3.8+" -optional = false -python-versions = ">=3.8" -files = [ - {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"}, - {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, -] - -[[package]] -name = "urllib3" -version = "2.0.7" -description = "HTTP library with thread-safe connection pooling, file post, and more." -optional = false -python-versions = ">=3.7" -files = [ - {file = "urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e"}, - {file = "urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84"}, -] - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] -secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] -socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] - -[[package]] -name = "virtualenv" -version = "20.24.6" -description = "Virtual Python Environment builder" -optional = false -python-versions = ">=3.7" -files = [ - {file = "virtualenv-20.24.6-py3-none-any.whl", hash = "sha256:520d056652454c5098a00c0f073611ccbea4c79089331f60bf9d7ba247bb7381"}, - {file = "virtualenv-20.24.6.tar.gz", hash = "sha256:02ece4f56fbf939dbbc33c0715159951d6bf14aaf5457b092e4548e1382455af"}, -] - -[package.dependencies] -distlib = ">=0.3.7,<1" -filelock = ">=3.12.2,<4" -platformdirs = ">=3.9.1,<4" - -[package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] - -[[package]] -name = "watchdog" -version = "3.0.0" -description = "Filesystem events monitoring" -optional = false -python-versions = ">=3.7" -files = [ - {file = "watchdog-3.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:336adfc6f5cc4e037d52db31194f7581ff744b67382eb6021c868322e32eef41"}, - {file = "watchdog-3.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a70a8dcde91be523c35b2bf96196edc5730edb347e374c7de7cd20c43ed95397"}, - {file = "watchdog-3.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:adfdeab2da79ea2f76f87eb42a3ab1966a5313e5a69a0213a3cc06ef692b0e96"}, - {file = "watchdog-3.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2b57a1e730af3156d13b7fdddfc23dea6487fceca29fc75c5a868beed29177ae"}, - {file = "watchdog-3.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7ade88d0d778b1b222adebcc0927428f883db07017618a5e684fd03b83342bd9"}, - {file = "watchdog-3.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7e447d172af52ad204d19982739aa2346245cc5ba6f579d16dac4bfec226d2e7"}, - {file = "watchdog-3.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9fac43a7466eb73e64a9940ac9ed6369baa39b3bf221ae23493a9ec4d0022674"}, - {file = "watchdog-3.0.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8ae9cda41fa114e28faf86cb137d751a17ffd0316d1c34ccf2235e8a84365c7f"}, - {file = "watchdog-3.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:25f70b4aa53bd743729c7475d7ec41093a580528b100e9a8c5b5efe8899592fc"}, - {file = "watchdog-3.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4f94069eb16657d2c6faada4624c39464f65c05606af50bb7902e036e3219be3"}, - {file = "watchdog-3.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7c5f84b5194c24dd573fa6472685b2a27cc5a17fe5f7b6fd40345378ca6812e3"}, - {file = "watchdog-3.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3aa7f6a12e831ddfe78cdd4f8996af9cf334fd6346531b16cec61c3b3c0d8da0"}, - {file = "watchdog-3.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:233b5817932685d39a7896b1090353fc8efc1ef99c9c054e46c8002561252fb8"}, - {file = "watchdog-3.0.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:13bbbb462ee42ec3c5723e1205be8ced776f05b100e4737518c67c8325cf6100"}, - {file = "watchdog-3.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8f3ceecd20d71067c7fd4c9e832d4e22584318983cabc013dbf3f70ea95de346"}, - {file = "watchdog-3.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c9d8c8ec7efb887333cf71e328e39cffbf771d8f8f95d308ea4125bf5f90ba64"}, - {file = "watchdog-3.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:0e06ab8858a76e1219e68c7573dfeba9dd1c0219476c5a44d5333b01d7e1743a"}, - {file = "watchdog-3.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:d00e6be486affb5781468457b21a6cbe848c33ef43f9ea4a73b4882e5f188a44"}, - {file = "watchdog-3.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:c07253088265c363d1ddf4b3cdb808d59a0468ecd017770ed716991620b8f77a"}, - {file = "watchdog-3.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:5113334cf8cf0ac8cd45e1f8309a603291b614191c9add34d33075727a967709"}, - {file = "watchdog-3.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:51f90f73b4697bac9c9a78394c3acbbd331ccd3655c11be1a15ae6fe289a8c83"}, - {file = "watchdog-3.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:ba07e92756c97e3aca0912b5cbc4e5ad802f4557212788e72a72a47ff376950d"}, - {file = "watchdog-3.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:d429c2430c93b7903914e4db9a966c7f2b068dd2ebdd2fa9b9ce094c7d459f33"}, - {file = "watchdog-3.0.0-py3-none-win32.whl", hash = "sha256:3ed7c71a9dccfe838c2f0b6314ed0d9b22e77d268c67e015450a29036a81f60f"}, - {file = "watchdog-3.0.0-py3-none-win_amd64.whl", hash = "sha256:4c9956d27be0bb08fc5f30d9d0179a855436e655f046d288e2bcc11adfae893c"}, - {file = "watchdog-3.0.0-py3-none-win_ia64.whl", hash = "sha256:5d9f3a10e02d7371cd929b5d8f11e87d4bad890212ed3901f9b4d68767bee759"}, - {file = "watchdog-3.0.0.tar.gz", hash = "sha256:4d98a320595da7a7c5a18fc48cb633c2e73cda78f93cac2ef42d42bf609a33f9"}, -] - -[package.extras] -watchmedo = ["PyYAML (>=3.10)"] - -[[package]] -name = "wcwidth" -version = "0.2.9" -description = "Measures the displayed width of unicode strings in a terminal" -optional = false -python-versions = "*" -files = [ - {file = "wcwidth-0.2.9-py2.py3-none-any.whl", hash = "sha256:9a929bd8380f6cd9571a968a9c8f4353ca58d7cd812a4822bba831f8d685b223"}, - {file = "wcwidth-0.2.9.tar.gz", hash = "sha256:a675d1a4a2d24ef67096a04b85b02deeecd8e226f57b5e3a72dbb9ed99d27da8"}, -] - -[[package]] -name = "wheel" -version = "0.45.1" -description = "A built-package format for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "wheel-0.45.1-py3-none-any.whl", hash = "sha256:708e7481cc80179af0e556bbf0cc00b8444c7321e2700b8d8580231d13017248"}, - {file = "wheel-0.45.1.tar.gz", hash = "sha256:661e1abd9198507b1409a20c02106d9670b2576e916d58f520316666abca6729"}, -] - -[package.extras] -test = ["pytest (>=6.0.0)", "setuptools (>=65)"] - -[[package]] -name = "wrapt" -version = "1.15.0" -description = "Module for decorators, wrappers and monkey patching." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" -files = [ - {file = "wrapt-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ca1cccf838cd28d5a0883b342474c630ac48cac5df0ee6eacc9c7290f76b11c1"}, - {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e826aadda3cae59295b95343db8f3d965fb31059da7de01ee8d1c40a60398b29"}, - {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5fc8e02f5984a55d2c653f5fea93531e9836abbd84342c1d1e17abc4a15084c2"}, - {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:96e25c8603a155559231c19c0349245eeb4ac0096fe3c1d0be5c47e075bd4f46"}, - {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:40737a081d7497efea35ab9304b829b857f21558acfc7b3272f908d33b0d9d4c"}, - {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f87ec75864c37c4c6cb908d282e1969e79763e0d9becdfe9fe5473b7bb1e5f09"}, - {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:1286eb30261894e4c70d124d44b7fd07825340869945c79d05bda53a40caa079"}, - {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:493d389a2b63c88ad56cdc35d0fa5752daac56ca755805b1b0c530f785767d5e"}, - {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:58d7a75d731e8c63614222bcb21dd992b4ab01a399f1f09dd82af17bbfc2368a"}, - {file = "wrapt-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:21f6d9a0d5b3a207cdf7acf8e58d7d13d463e639f0c7e01d82cdb671e6cb7923"}, - {file = "wrapt-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce42618f67741d4697684e501ef02f29e758a123aa2d669e2d964ff734ee00ee"}, - {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41d07d029dd4157ae27beab04d22b8e261eddfc6ecd64ff7000b10dc8b3a5727"}, - {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54accd4b8bc202966bafafd16e69da9d5640ff92389d33d28555c5fd4f25ccb7"}, - {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fbfbca668dd15b744418265a9607baa970c347eefd0db6a518aaf0cfbd153c0"}, - {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:76e9c727a874b4856d11a32fb0b389afc61ce8aaf281ada613713ddeadd1cfec"}, - {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e20076a211cd6f9b44a6be58f7eeafa7ab5720eb796975d0c03f05b47d89eb90"}, - {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a74d56552ddbde46c246b5b89199cb3fd182f9c346c784e1a93e4dc3f5ec9975"}, - {file = "wrapt-1.15.0-cp310-cp310-win32.whl", hash = "sha256:26458da5653aa5b3d8dc8b24192f574a58984c749401f98fff994d41d3f08da1"}, - {file = "wrapt-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:75760a47c06b5974aa5e01949bf7e66d2af4d08cb8c1d6516af5e39595397f5e"}, - {file = "wrapt-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ba1711cda2d30634a7e452fc79eabcadaffedf241ff206db2ee93dd2c89a60e7"}, - {file = "wrapt-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:56374914b132c702aa9aa9959c550004b8847148f95e1b824772d453ac204a72"}, - {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a89ce3fd220ff144bd9d54da333ec0de0399b52c9ac3d2ce34b569cf1a5748fb"}, - {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bbe623731d03b186b3d6b0d6f51865bf598587c38d6f7b0be2e27414f7f214e"}, - {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3abbe948c3cbde2689370a262a8d04e32ec2dd4f27103669a45c6929bcdbfe7c"}, - {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b67b819628e3b748fd3c2192c15fb951f549d0f47c0449af0764d7647302fda3"}, - {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7eebcdbe3677e58dd4c0e03b4f2cfa346ed4049687d839adad68cc38bb559c92"}, - {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:74934ebd71950e3db69960a7da29204f89624dde411afbfb3b4858c1409b1e98"}, - {file = "wrapt-1.15.0-cp311-cp311-win32.whl", hash = "sha256:bd84395aab8e4d36263cd1b9308cd504f6cf713b7d6d3ce25ea55670baec5416"}, - {file = "wrapt-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:a487f72a25904e2b4bbc0817ce7a8de94363bd7e79890510174da9d901c38705"}, - {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:4ff0d20f2e670800d3ed2b220d40984162089a6e2c9646fdb09b85e6f9a8fc29"}, - {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9ed6aa0726b9b60911f4aed8ec5b8dd7bf3491476015819f56473ffaef8959bd"}, - {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:896689fddba4f23ef7c718279e42f8834041a21342d95e56922e1c10c0cc7afb"}, - {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:75669d77bb2c071333417617a235324a1618dba66f82a750362eccbe5b61d248"}, - {file = "wrapt-1.15.0-cp35-cp35m-win32.whl", hash = "sha256:fbec11614dba0424ca72f4e8ba3c420dba07b4a7c206c8c8e4e73f2e98f4c559"}, - {file = "wrapt-1.15.0-cp35-cp35m-win_amd64.whl", hash = "sha256:fd69666217b62fa5d7c6aa88e507493a34dec4fa20c5bd925e4bc12fce586639"}, - {file = "wrapt-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b0724f05c396b0a4c36a3226c31648385deb6a65d8992644c12a4963c70326ba"}, - {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbeccb1aa40ab88cd29e6c7d8585582c99548f55f9b2581dfc5ba68c59a85752"}, - {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38adf7198f8f154502883242f9fe7333ab05a5b02de7d83aa2d88ea621f13364"}, - {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:578383d740457fa790fdf85e6d346fda1416a40549fe8db08e5e9bd281c6a475"}, - {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a4cbb9ff5795cd66f0066bdf5947f170f5d63a9274f99bdbca02fd973adcf2a8"}, - {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:af5bd9ccb188f6a5fdda9f1f09d9f4c86cc8a539bd48a0bfdc97723970348418"}, - {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b56d5519e470d3f2fe4aa7585f0632b060d532d0696c5bdfb5e8319e1d0f69a2"}, - {file = "wrapt-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:77d4c1b881076c3ba173484dfa53d3582c1c8ff1f914c6461ab70c8428b796c1"}, - {file = "wrapt-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:077ff0d1f9d9e4ce6476c1a924a3332452c1406e59d90a2cf24aeb29eeac9420"}, - {file = "wrapt-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5c5aa28df055697d7c37d2099a7bc09f559d5053c3349b1ad0c39000e611d317"}, - {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a8564f283394634a7a7054b7983e47dbf39c07712d7b177b37e03f2467a024e"}, - {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780c82a41dc493b62fc5884fb1d3a3b81106642c5c5c78d6a0d4cbe96d62ba7e"}, - {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e169e957c33576f47e21864cf3fc9ff47c223a4ebca8960079b8bd36cb014fd0"}, - {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b02f21c1e2074943312d03d243ac4388319f2456576b2c6023041c4d57cd7019"}, - {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f2e69b3ed24544b0d3dbe2c5c0ba5153ce50dcebb576fdc4696d52aa22db6034"}, - {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d787272ed958a05b2c86311d3a4135d3c2aeea4fc655705f074130aa57d71653"}, - {file = "wrapt-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:02fce1852f755f44f95af51f69d22e45080102e9d00258053b79367d07af39c0"}, - {file = "wrapt-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:abd52a09d03adf9c763d706df707c343293d5d106aea53483e0ec8d9e310ad5e"}, - {file = "wrapt-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cdb4f085756c96a3af04e6eca7f08b1345e94b53af8921b25c72f096e704e145"}, - {file = "wrapt-1.15.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:230ae493696a371f1dbffaad3dafbb742a4d27a0afd2b1aecebe52b740167e7f"}, - {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63424c681923b9f3bfbc5e3205aafe790904053d42ddcc08542181a30a7a51bd"}, - {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6bcbfc99f55655c3d93feb7ef3800bd5bbe963a755687cbf1f490a71fb7794b"}, - {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c99f4309f5145b93eca6e35ac1a988f0dc0a7ccf9ccdcd78d3c0adf57224e62f"}, - {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b130fe77361d6771ecf5a219d8e0817d61b236b7d8b37cc045172e574ed219e6"}, - {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:96177eb5645b1c6985f5c11d03fc2dbda9ad24ec0f3a46dcce91445747e15094"}, - {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5fe3e099cf07d0fb5a1e23d399e5d4d1ca3e6dfcbe5c8570ccff3e9208274f7"}, - {file = "wrapt-1.15.0-cp38-cp38-win32.whl", hash = "sha256:abd8f36c99512755b8456047b7be10372fca271bf1467a1caa88db991e7c421b"}, - {file = "wrapt-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:b06fa97478a5f478fb05e1980980a7cdf2712015493b44d0c87606c1513ed5b1"}, - {file = "wrapt-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2e51de54d4fb8fb50d6ee8327f9828306a959ae394d3e01a1ba8b2f937747d86"}, - {file = "wrapt-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0970ddb69bba00670e58955f8019bec4a42d1785db3faa043c33d81de2bf843c"}, - {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76407ab327158c510f44ded207e2f76b657303e17cb7a572ffe2f5a8a48aa04d"}, - {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd525e0e52a5ff16653a3fc9e3dd827981917d34996600bbc34c05d048ca35cc"}, - {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d37ac69edc5614b90516807de32d08cb8e7b12260a285ee330955604ed9dd29"}, - {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:078e2a1a86544e644a68422f881c48b84fef6d18f8c7a957ffd3f2e0a74a0d4a"}, - {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2cf56d0e237280baed46f0b5316661da892565ff58309d4d2ed7dba763d984b8"}, - {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7dc0713bf81287a00516ef43137273b23ee414fe41a3c14be10dd95ed98a2df9"}, - {file = "wrapt-1.15.0-cp39-cp39-win32.whl", hash = "sha256:46ed616d5fb42f98630ed70c3529541408166c22cdfd4540b88d5f21006b0eff"}, - {file = "wrapt-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:eef4d64c650f33347c1f9266fa5ae001440b232ad9b98f1f43dfe7a79435c0a6"}, - {file = "wrapt-1.15.0-py3-none-any.whl", hash = "sha256:64b1df0f83706b4ef4cfb4fb0e4c2669100fd7ecacfb59e091fad300d4e04640"}, - {file = "wrapt-1.15.0.tar.gz", hash = "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a"}, -] - -[[package]] -name = "zipp" -version = "3.17.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -optional = false -python-versions = ">=3.8" -files = [ - {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"}, - {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"}, -] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] - -[metadata] -lock-version = "2.0" -python-versions = ">=3.8,<4.0" -content-hash = "70753e4dbedfbbaacd9441d2fb9e2874db34010b2f9e2cfef92250879abf195d" diff --git a/pyproject.toml b/pyproject.toml index 0b106bbd..827984b7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "photoshop-python-api" -version = "0.22.10" +version = "0.22.7" description = "Python API for Photoshop." homepage = "https://github.com/loonghao/photoshop-python-api" repository = "https://github.com/loonghao/photoshop-python-api" @@ -14,7 +14,7 @@ classifiers = [ "License :: OSI Approved :: MIT License", "Operating System :: Microsoft :: Windows", "Operating System :: Microsoft :: Windows :: Windows 10", - "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", @@ -25,59 +25,30 @@ classifiers = [ packages = [ { include = "photoshop" }, ] + [tool.poetry.build] generate-setup-file = false [tool.poetry.dependencies] python = ">=3.8,<4.0" -wheel = "^0.45.0" -comtypes = "^1.1.11" +comtypes = "^1.2.0" [tool.poetry.group.dev.dependencies] -commitizen = "^2.17.8" -pre-commit = "^2.13.0" -codecov = "^2.1.11" -pylint = "^2.8.2" -isort = "^5.8.0" -pytest = "^7.0.0" -flake8 = "^4.0.0" -mypy = "^1.0" -coverage = "^7.0.0" -mkdocs = "^1.2.2" -mkdocs-git-revision-date-plugin = "^0.3.1" -black = "^23.0.0" -mkdocs-material = "^8.2.5" -mkdocstrings-python = "^1.0.0" -mkdocs-pymdownx-material-extras = "^2.0" -mkdocs-same-dir = "^0.1.1" -mkdocs-include-markdown-plugin = "^3.3.0" -mkdocs-gen-files = "^0.5.0" -mkdocs-autolinks-plugin = "^0.7.0" -mkdocs-minify-plugin = "^0.5.0" -mkdocs-git-revision-date-localized-plugin = "^1.0.0" -pytest-cov = "^4.0.0" -stringcase = "^1.2.0" -mkdocs-literate-nav = "^0.6.0" +nox = "^2023.4.22" +nox-poetry = "^1.0.3" -[tool.commitizen] -name = "cz_conventional_commits" -version = "0.22.10" -tag_format = "v$version" -version_files = [ - "pyproject.toml:version", - "photoshop/__version__.py" -] +[tool.poetry.urls] +"Bug Tracker" = "https://github.com/loonghao/photoshop-python-api/issues" [build-system] -requires = ["poetry-core>=1.0.0"] +requires = ["poetry-core>=1.0.0", "wheel>=0.42.0"] build-backend = "poetry.core.masonry.api" [tool.black] line-length = 120 -target_version = ['py37'] +target-version = ['py38', 'py39', 'py310', 'py311', 'py312'] include = '\.pyi?$' exclude = ''' - ( /( \.eggs # exclude a few common directories in the @@ -90,6 +61,7 @@ exclude = ''' | buck-out | build | dist + | manual_test # exclude manual test directory )/ | foo.py # also separately exclude a file named foo.py in # the root of the project @@ -98,14 +70,16 @@ exclude = ''' [tool.isort] profile = "black" -atomic = true +multi_line_output = 3 include_trailing_comma = true -lines_after_imports = 2 -lines_between_types = 1 +force_grid_wrap = 0 use_parentheses = true +ensure_newline_before_comments = true +line_length = 120 src_paths = ["photoshop", "test"] filter_files = true known_first_party = "photoshop" +extend_skip = ["manual_test"] # Add manual_test to isort skip list # Enforce import section headers. import_heading_future = "Import future modules" @@ -118,4 +92,121 @@ force_single_line = true # All project unrelated unknown imports belong to third-party. default_section = "THIRDPARTY" -skip_glob = "*/docs/conf.py" +skip_glob = ["*/docs/conf.py", "manual_test/**/*.py"] + +[tool.ruff] +line-length = 120 +target-version = "py38" +extend-exclude = ["manual_test"] # Add manual_test to ruff exclude list + +[tool.ruff.lint] +select = [ + "E", # pycodestyle errors + "F", # pyflakes + "UP", # pyupgrade + "B", # flake8-bugbear + "S", # flake8-bandit + "C4", # flake8-comprehensions + "DTZ", # flake8-datetimez + "T10", # flake8-debugger + "ISC", # flake8-implicit-str-concat + "ICN", # flake8-import-conventions + "G", # flake8-logging-format + "PIE", # flake8-pie + "T20", # flake8-print + "PYI", # flake8-pyi + "PT", # flake8-pytest-style + "Q", # flake8-quotes + "RSE", # flake8-raise + "RET", # flake8-return + "SLF", # flake8-self + "SIM", # flake8-simplify + "TID", # flake8-tidy-imports + "TCH", # flake8-type-checking + "ARG", # flake8-unused-arguments + "PTH", # flake8-use-pathlib + "ERA", # eradicate + "PGH", # pygrep-hooks + "PL", # pylint + "TRY", # tryceratops + "RUF", # ruff-specific rules + "FBT", # boolean-trap + "A", # flake8-builtins + "COM", # flake8-commas + "CPY", # flake8-copyright + "C90", # mccabe + "EM", # flake8-errmsg + "FA", # flake8-future-annotations + "FIX", # flake8-fixme + "FLY", # flynt + "INP", # flake8-no-pep420 + "LOG", # flake8-logging + "N", # pep8-naming + "D", # pydocstyle + "ANN", # flake8-annotations +] + +ignore = [ + "E501", # line too long + "E402", # module level import not at top of file + "E731", # do not assign a lambda expression, use a def + "E741", # ambiguous variable name + "N802", # function name should be lowercase + "N803", # argument name should be lowercase + "N806", # variable in function should be lowercase + "N815", # argument name should not be a magic global variable +] + +[tool.ruff.lint.per-file-ignores] +"__init__.py" = ["E402"] +"test_*.py" = ["S101"] + +[tool.ruff.lint.isort] +known-first-party = ["photoshop"] +section-order = ["future", "standard-library", "third-party", "first-party", "local-folder"] +required-imports = ["from __future__ import annotations"] + +[tool.ruff.lint.isort.sections] +future = ["__future__"] +standard-library = ["typing", "os", "sys", "datetime"] +third-party = ["comtypes", "win32com"] +first-party = ["photoshop"] +local-folder = ["photoshop.api"] + +[tool.ruff.lint.pydocstyle] +convention = "google" + +[tool.commitizen] +name = "cz_conventional_commits" +version = "0.22.7" +tag_format = "v$version" +version_files = [ + "pyproject.toml:version", + "photoshop/__version__.py" +] + +[tool.mypy] +python_version = "3.8" +warn_return_any = true +warn_unused_configs = true +disallow_untyped_defs = true +disallow_incomplete_defs = true +check_untyped_defs = true +disallow_untyped_decorators = true +no_implicit_optional = true +warn_redundant_casts = true +warn_unused_ignores = true +warn_no_return = true +warn_unreachable = true +exclude = ["manual_test/.*"] # Add manual_test to mypy exclude list +ignore_missing_imports = true +allow_redefinition = true +disable_error_code = ["union-attr", "no-any-return", "assignment", "misc", "arg-type", "index", "attr-defined", "type-var"] + +[[tool.mypy.overrides]] +module = "comtypes.*" +ignore_missing_imports = true + +[[tool.mypy.overrides]] +module = "win32com.*" +ignore_missing_imports = true diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 00000000..4e7b9ea7 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,4 @@ +poetry +nox +pytest +nox-poetry diff --git a/test/conftest.py b/test/conftest.py index 4c3929bf..d8f6faf9 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -1,29 +1,31 @@ -# Import built-in modules -import os - -# Import third-party modules -import pytest - - -@pytest.fixture() -def photoshop_app(): - # Import local modules - from photoshop.api import Application - - app = Application() - app.documents.add(name="UnitTest") - yield app - app.activeDocument.close() - - -@pytest.fixture() -def data_root(): - return os.path.join(os.path.dirname(__file__), "test_data") - - -@pytest.fixture() -def psd_file(data_root): - def _get_psd_file(name): - return os.path.join(data_root, f"{name}.psd") - - return _get_psd_file +# Import built-in modules +from __future__ import annotations + +import os + +# Import third-party modules +import pytest + + +@pytest.fixture +def photoshop_app(): + # Import local modules + from photoshop.api import Application + + app = Application() + app.documents.add(name="UnitTest") + yield app + app.activeDocument.close() + + +@pytest.fixture +def data_root(): + return os.path.join(os.path.dirname(__file__), "test_data") + + +@pytest.fixture +def psd_file(data_root): + def _get_psd_file(name): + return os.path.join(data_root, f"{name}.psd") + + return _get_psd_file diff --git a/test/manual_test/__init__.py b/test/manual_test/__init__.py new file mode 100644 index 00000000..250d79ed --- /dev/null +++ b/test/manual_test/__init__.py @@ -0,0 +1,3 @@ +"""Manual test cases for photoshop-python-api.""" + +from __future__ import annotations diff --git a/test/manual_test/manual_test_all_examples.py b/test/manual_test/manual_test_all_examples.py index a21a0190..e16f0be0 100644 --- a/test/manual_test/manual_test_all_examples.py +++ b/test/manual_test/manual_test_all_examples.py @@ -1,21 +1,22 @@ -"""Manual test all examples.""" - -# Import built-in modules -from pathlib import Path - -# Import local modules -from photoshop.api import Application - - -root = Path(__file__).parent.parent.parent.joinpath("examples") -for script_file in root.glob("*.py"): - try: - exec(script_file.read_text()) - except Exception as err: - print(f"Test failed: {script_file}", str(err), end="\n") - -# Clear up and close all documents. -app = Application() - -while app.documents.length: - app.activeDocument.close() +"""Manual test all examples.""" + +# Import built-in modules +from __future__ import annotations + +from pathlib import Path + +# Import local modules +from photoshop.api import Application + +root = Path(__file__).parent.parent.parent.joinpath("examples") +for script_file in root.glob("*.py"): + try: + exec(script_file.read_text()) + except Exception as err: + print(f"Test failed: {script_file}", str(err), end="\n") + +# Clear up and close all documents. +app = Application() + +while app.documents.length: + app.activeDocument.close() diff --git a/test/manual_test/manual_test_application.py b/test/manual_test/manual_test_application.py index 26296062..a4296c06 100644 --- a/test/manual_test/manual_test_application.py +++ b/test/manual_test/manual_test_application.py @@ -1,221 +1,221 @@ -"""""" -# Import third-party modules -import pytest - -# Import local modules -from photoshop.api import Application -from photoshop.api import EventID -from photoshop.api import SolidColor - - -class TestApplication: - """Test the solidColor.""" - - # pylint: disable=attribute-defined-outside-init - @pytest.fixture(autouse=True) - def setup(self): - self.app = Application() - color = SolidColor() - color.rgb.red = 255 - color.rgb.green = 111 - self.app.backgroundColor = color - foreground_color = SolidColor() - foreground_color.rgb.green = 0 - self.app.foregroundColor = foreground_color - self.app.currentTool = "moveTool" - self.app.notifiers.removeAll() - - def test_active_document(self, photoshop_app): - assert photoshop_app.activeDocument.name == self.app.activeDocument.name - - def test_get_background_color(self): - assert self.app.backgroundColor.rgb.red == 255 - assert self.app.backgroundColor.rgb.green == 111 - assert self.app.backgroundColor.rgb.blue == 255 - assert self.app.backgroundColor.cmyk.yellow == 0 - assert self.app.backgroundColor.cmyk.magenta == 60 - assert self.app.backgroundColor.cmyk.cyan == 17 - assert self.app.backgroundColor.cmyk.black == 0 - assert self.app.backgroundColor.hsb.hue == 300 - assert self.app.backgroundColor.hsb.saturation == 56 - assert self.app.backgroundColor.hsb.brightness == 100 - assert self.app.backgroundColor.lab.A == 68 - assert self.app.backgroundColor.lab.B == -46 - assert self.app.backgroundColor.lab.L == 69 - - 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 - - def test_build(self): - assert self.app.build == "21.0 (20191018.r.37 2019/10/18: 614690fb487)" - - def test_get_color_settings(self): - assert self.app.colorSettings == "North America General Purpose 2" - - def test_set_color_settings(self, photoshop_app): - color_settings = photoshop_app.colorSettings - self.app.colorSettings = "Monitor Color" - assert photoshop_app.colorSettings == "Monitor Color" - self.app.colorSettings = color_settings - - def test_get_current_tool(self): - assert self.app.currentTool == "moveTool" - - def test_set_current_tool(self): - self.app.currentTool = "typeCreateOrEditTool" - assert self.app.currentTool == "typeCreateOrEditTool" - - def test_displayDialogs(self): - assert str(self.app.displayDialogs) == "DialogModes.DisplayErrorDialogs" - - def test_documents(self): - assert len(self.app.documents) == 0 - - def test_get_fonts_count(self): - assert self.app.fonts.length == 440 - assert len(self.app.fonts) == 440 - - def test_get_foreground_color(self): - assert self.app.foregroundColor.rgb.red == 255 - assert self.app.foregroundColor.rgb.green == 0 - assert self.app.foregroundColor.rgb.blue == 255 - assert self.app.foregroundColor.cmyk.yellow == 0 - assert self.app.foregroundColor.cmyk.magenta == 82 - assert self.app.foregroundColor.cmyk.cyan == 27 - assert self.app.foregroundColor.cmyk.black == 0 - assert self.app.foregroundColor.hsb.hue == 300 - assert self.app.foregroundColor.hsb.saturation == 100 - assert self.app.foregroundColor.hsb.brightness == 100 - assert self.app.foregroundColor.lab.A == 93 - assert self.app.foregroundColor.lab.B == -61 - assert self.app.foregroundColor.lab.L == 60 - - def test_get_free_memory(self): - assert self.app.freeMemory - - def test_get_locale(self): - assert self.app.locale == "en_US" - - def test_macintoshFileTypes(self): - assert "JPEG" in self.app.macintoshFileTypes - - def test_get_name(self): - assert self.app.name == "Adobe Photoshop" - - def test_notifiers(self): - assert self.app.notifiers.length == 0 - - def test_add_notifiers(self, tmpdir): - jsx_file = tmpdir.join("event.jsx") - jsx_file.write('alert("Test Event")') - self.app.notifiers.add(EventID.Open, str(jsx_file)) - assert self.app.notifiers.length == 1 - assert self.app.notifiers[0].EventID == EventID.Open - - def test_get_notifiersEnabled(self): - assert not self.app.notifiersEnabled - - def test_get_application_path(self): - assert self.app.path.as_posix() == "C:/Program Files/Adobe/Adobe Photoshop 2020" - - def test_playbackDisplayDialogs(self): - assert self.app.playbackDisplayDialogs == "DialogModes.NO" - - def test_playbackParameters(self): - # assert self.app.playbackParameters - print("Need test.") - - def test_preferences(self): - assert self.app.preferences - - def test_get_preferencesFolder(self): - assert self.app.preferencesFolder.is_dir() - - def test_get_recentFiles(self): - assert self.app.recentFiles - - def test_scriptingBuildDate(self): - assert self.app.scriptingBuildDate == "Apr 10 2020 00:39:52" - - def test_get_scriptingVersion(self): - assert self.app.scriptingVersion == "21.1" - - def test_get_systemInformation(self): - assert self.app.systemInformation - - def test_get_typename(self): - assert self.app.typename == "Application" - - def test_get_version(self): - assert self.app.version == "21.1.2" - - def test_windowsFileTypes(self): - assert len(self.app.windowsFileTypes) >= 100 - - def test_batch(self): - self.app.batch() - - def test_beep(self): - self.app.beep() - - def test_bringToFront(self): - self.app.bringToFront() - - def test_changeProgressText(self): - self.app.changeProgressText("test") - - 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") - - def test_featureEnabled(self): - assert self.app.featureEnabled("photoshop/extended") - - # def test_getCustomOptions(self): - # assert self.app.getCustomOptions("Application") - - def test_isQuicktimeAvailable(self): - assert self.app.isQuicktimeAvailable - - def test_openDialog(self): - assert self.app.openDialog() - - def test_refresh(self): - return self.app.refresh() - - def test_refreshFonts(self): - return self.app.refreshFonts() - - def test_run_menu_item(self): - assert self.app.runMenuItem(self.app.stringIDToTypeID("toggleProofColors")) - - def test_showColorPicker(self): - assert self.app.showColorPicker() - - def test_stringIDToTypeID(self): - assert self.app.stringIDToTypeID("toggleProofColors") == 2034 - - def test_togglePalettes(self): - assert self.app.togglePalettes() - self.app.togglePalettes() - - def test_toolSupportsBrushes(self): - assert self.app.toolSupportsBrushes("Tool") - - def test_toolSupportsBrushPresets(self): - assert self.app.toolSupportsBrushPresets("Tool") - - def test_typeIDToCharID(self): - assert self.app.typeIDToCharID("toggleProofColors") - - def test_typeIDToStringID(self): - assert self.app.typeIDToStringID("toggleProofColors") - - def test_updateProgress(self): - assert self.app.updateProgress("Done", "total") +"""""" +# Import third-party modules +from __future__ import annotations + +import pytest + +# Import local modules +from photoshop.api import Application, EventID, SolidColor + + +class TestApplication: + """Test the solidColor.""" + + # pylint: disable=attribute-defined-outside-init + @pytest.fixture(autouse=True) + def setup(self): + self.app = Application() + color = SolidColor() + color.rgb.red = 255 + color.rgb.green = 111 + self.app.backgroundColor = color + foreground_color = SolidColor() + foreground_color.rgb.green = 0 + self.app.foregroundColor = foreground_color + self.app.currentTool = "moveTool" + self.app.notifiers.removeAll() + + def test_active_document(self, photoshop_app): + assert photoshop_app.activeDocument.name == self.app.activeDocument.name + + def test_get_background_color(self): + assert self.app.backgroundColor.rgb.red == 255 + assert self.app.backgroundColor.rgb.green == 111 + assert self.app.backgroundColor.rgb.blue == 255 + assert self.app.backgroundColor.cmyk.yellow == 0 + assert self.app.backgroundColor.cmyk.magenta == 60 + assert self.app.backgroundColor.cmyk.cyan == 17 + assert self.app.backgroundColor.cmyk.black == 0 + assert self.app.backgroundColor.hsb.hue == 300 + assert self.app.backgroundColor.hsb.saturation == 56 + assert self.app.backgroundColor.hsb.brightness == 100 + assert self.app.backgroundColor.lab.A == 68 + assert self.app.backgroundColor.lab.B == -46 + assert self.app.backgroundColor.lab.L == 69 + + 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 + + def test_build(self): + assert self.app.build == "21.0 (20191018.r.37 2019/10/18: 614690fb487)" + + def test_get_color_settings(self): + assert self.app.colorSettings == "North America General Purpose 2" + + def test_set_color_settings(self, photoshop_app): + color_settings = photoshop_app.colorSettings + self.app.colorSettings = "Monitor Color" + assert photoshop_app.colorSettings == "Monitor Color" + self.app.colorSettings = color_settings + + def test_get_current_tool(self): + assert self.app.currentTool == "moveTool" + + def test_set_current_tool(self): + self.app.currentTool = "typeCreateOrEditTool" + assert self.app.currentTool == "typeCreateOrEditTool" + + def test_displayDialogs(self): + assert str(self.app.displayDialogs) == "DialogModes.DisplayErrorDialogs" + + def test_documents(self): + assert len(self.app.documents) == 0 + + def test_get_fonts_count(self): + assert self.app.fonts.length == 440 + assert len(self.app.fonts) == 440 + + def test_get_foreground_color(self): + assert self.app.foregroundColor.rgb.red == 255 + assert self.app.foregroundColor.rgb.green == 0 + assert self.app.foregroundColor.rgb.blue == 255 + assert self.app.foregroundColor.cmyk.yellow == 0 + assert self.app.foregroundColor.cmyk.magenta == 82 + assert self.app.foregroundColor.cmyk.cyan == 27 + assert self.app.foregroundColor.cmyk.black == 0 + assert self.app.foregroundColor.hsb.hue == 300 + assert self.app.foregroundColor.hsb.saturation == 100 + assert self.app.foregroundColor.hsb.brightness == 100 + assert self.app.foregroundColor.lab.A == 93 + assert self.app.foregroundColor.lab.B == -61 + assert self.app.foregroundColor.lab.L == 60 + + def test_get_free_memory(self): + assert self.app.freeMemory + + def test_get_locale(self): + assert self.app.locale == "en_US" + + def test_macintoshFileTypes(self): + assert "JPEG" in self.app.macintoshFileTypes + + def test_get_name(self): + assert self.app.name == "Adobe Photoshop" + + def test_notifiers(self): + assert self.app.notifiers.length == 0 + + def test_add_notifiers(self, tmpdir): + jsx_file = tmpdir.join("event.jsx") + jsx_file.write('alert("Test Event")') + self.app.notifiers.add(EventID.Open, str(jsx_file)) + assert self.app.notifiers.length == 1 + assert self.app.notifiers[0].EventID == EventID.Open + + def test_get_notifiersEnabled(self): + assert not self.app.notifiersEnabled + + def test_get_application_path(self): + assert self.app.path.as_posix() == "C:/Program Files/Adobe/Adobe Photoshop 2020" + + def test_playbackDisplayDialogs(self): + assert self.app.playbackDisplayDialogs == "DialogModes.NO" + + def test_playbackParameters(self): + # assert self.app.playbackParameters + print("Need test.") + + def test_preferences(self): + assert self.app.preferences + + def test_get_preferencesFolder(self): + assert self.app.preferencesFolder.is_dir() + + def test_get_recentFiles(self): + assert self.app.recentFiles + + def test_scriptingBuildDate(self): + assert self.app.scriptingBuildDate == "Apr 10 2020 00:39:52" + + def test_get_scriptingVersion(self): + assert self.app.scriptingVersion == "21.1" + + def test_get_systemInformation(self): + assert self.app.systemInformation + + def test_get_typename(self): + assert self.app.typename == "Application" + + def test_get_version(self): + assert self.app.version == "21.1.2" + + def test_windowsFileTypes(self): + assert len(self.app.windowsFileTypes) >= 100 + + def test_batch(self): + self.app.batch() + + def test_beep(self): + self.app.beep() + + def test_bringToFront(self): + self.app.bringToFront() + + def test_changeProgressText(self): + self.app.changeProgressText("test") + + 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") + + def test_featureEnabled(self): + assert self.app.featureEnabled("photoshop/extended") + + # def test_getCustomOptions(self): + # assert self.app.getCustomOptions("Application") + + def test_isQuicktimeAvailable(self): + assert self.app.isQuicktimeAvailable + + def test_openDialog(self): + assert self.app.openDialog() + + def test_refresh(self): + return self.app.refresh() + + def test_refreshFonts(self): + return self.app.refreshFonts() + + def test_run_menu_item(self): + assert self.app.runMenuItem(self.app.stringIDToTypeID("toggleProofColors")) + + def test_showColorPicker(self): + assert self.app.showColorPicker() + + def test_stringIDToTypeID(self): + assert self.app.stringIDToTypeID("toggleProofColors") == 2034 + + def test_togglePalettes(self): + assert self.app.togglePalettes() + self.app.togglePalettes() + + def test_toolSupportsBrushes(self): + assert self.app.toolSupportsBrushes("Tool") + + def test_toolSupportsBrushPresets(self): + assert self.app.toolSupportsBrushPresets("Tool") + + def test_typeIDToCharID(self): + assert self.app.typeIDToCharID("toggleProofColors") + + def test_typeIDToStringID(self): + assert self.app.typeIDToStringID("toggleProofColors") + + def test_updateProgress(self): + assert self.app.updateProgress("Done", "total") diff --git a/test/manual_test/manual_test_layer_comps.py b/test/manual_test/manual_test_layer_comps.py index efc269de..f66a5c1a 100644 --- a/test/manual_test/manual_test_layer_comps.py +++ b/test/manual_test/manual_test_layer_comps.py @@ -1,36 +1,38 @@ -"""""" -# Import third-party modules -import pytest - -# Import local modules -from photoshop import Session - - -class TestTextItem: - """Test the solidColor.""" - - # pylint: disable=attribute-defined-outside-init - @pytest.fixture(autouse=True) - def setup(self, psd_file): - """Setup for current test.""" - self.session = Session(file_path=psd_file("layer_comps"), action="open", auto_close=True) - self.session.run_action() - doc = self.session.active_document - self.layer_compse = doc.layerComps # -> TextItem - yield - self.session.close() - - def test_length(self): - assert self.layer_compse.length == 2 - - def test_getByName(self): - layer = self.layer_compse.getByName("layer1") - assert layer.name == "layer1" - - def test_loop_layers(self): - for layer in self.layer_compse: - assert layer.name - - def test_add_a_layer(self): - self.layer_compse.add("new_layer", "test") - assert self.layer_compse.length == 3 +"""""" +# Import third-party modules +from __future__ import annotations + +import pytest + +# Import local modules +from photoshop import Session + + +class TestTextItem: + """Test the solidColor.""" + + # pylint: disable=attribute-defined-outside-init + @pytest.fixture(autouse=True) + def setup(self, psd_file): + """Setup for current test.""" + self.session = Session(file_path=psd_file("layer_comps"), action="open", auto_close=True) + self.session.run_action() + doc = self.session.active_document + self.layer_compse = doc.layerComps # -> TextItem + yield + self.session.close() + + def test_length(self): + assert self.layer_compse.length == 2 + + def test_getByName(self): + layer = self.layer_compse.getByName("layer1") + assert layer.name == "layer1" + + def test_loop_layers(self): + for layer in self.layer_compse: + assert layer.name + + def test_add_a_layer(self): + self.layer_compse.add("new_layer", "test") + assert self.layer_compse.length == 3 diff --git a/test/manual_test/manual_test_solid_color.py b/test/manual_test/manual_test_solid_color.py index 20229302..191a95d3 100644 --- a/test/manual_test/manual_test_solid_color.py +++ b/test/manual_test/manual_test_solid_color.py @@ -1,43 +1,45 @@ -"""""" -# Import third-party modules -import pytest - -# Import local modules -from photoshop import Session - - -class TestSolidColor: - """Test the solidColor.""" - - # pylint: disable=attribute-defined-outside-init - @pytest.fixture(autouse=True) - def setup(self): - self.session = Session() - self.solid_color = self.session.SolidColor() - - def test_cmyk_black(self): - assert self.solid_color.cmyk.black == 0 - - def test_cmyk_cyan(self): - assert self.solid_color.cmyk.cyan == 0 - - def test_cmyk_magenta(self): - assert self.solid_color.cmyk.magenta == 0 - - def test_cmyk_typename(self): - assert self.solid_color.cmyk.typename == "CMYKColor" - - def test_yellow(self): - assert self.solid_color.cmyk.yellow == 0 - - def test_hsb_brightness(self): - assert self.solid_color.hsb.brightness == 100 - - def test_hsb_hue(self): - assert self.solid_color.hsb.hue == 0 - - def test_hsb_saturation(self): - assert self.solid_color.hsb.saturation == 0 - - def test_hsb_typename(self): - assert self.solid_color.hsb.typename == "HSBColor" +"""""" +# Import third-party modules +from __future__ import annotations + +import pytest + +# Import local modules +from photoshop import Session + + +class TestSolidColor: + """Test the solidColor.""" + + # pylint: disable=attribute-defined-outside-init + @pytest.fixture(autouse=True) + def setup(self): + self.session = Session() + self.solid_color = self.session.SolidColor() + + def test_cmyk_black(self): + assert self.solid_color.cmyk.black == 0 + + def test_cmyk_cyan(self): + assert self.solid_color.cmyk.cyan == 0 + + def test_cmyk_magenta(self): + assert self.solid_color.cmyk.magenta == 0 + + def test_cmyk_typename(self): + assert self.solid_color.cmyk.typename == "CMYKColor" + + def test_yellow(self): + assert self.solid_color.cmyk.yellow == 0 + + def test_hsb_brightness(self): + assert self.solid_color.hsb.brightness == 100 + + def test_hsb_hue(self): + assert self.solid_color.hsb.hue == 0 + + def test_hsb_saturation(self): + assert self.solid_color.hsb.saturation == 0 + + def test_hsb_typename(self): + assert self.solid_color.hsb.typename == "HSBColor" diff --git a/test/manual_test/manual_test_text_item.py b/test/manual_test/manual_test_text_item.py index 08f1b120..3c1d8257 100644 --- a/test/manual_test/manual_test_text_item.py +++ b/test/manual_test/manual_test_text_item.py @@ -1,97 +1,98 @@ -"""""" -# Import third-party modules -import pytest - -# Import local modules -from photoshop import Session -from photoshop.api.enumerations import TextType - - -class TestTextItem: - """Test the solidColor.""" - - # pylint: disable=attribute-defined-outside-init - @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.run_action() - doc = self.session.active_document - layer = doc.activeLayer - self.text_item = layer.textItem() # -> TextItem - yield - # self.session.close() - - def manual_test_alternateLigatures(self): - assert self.text_item.alternateLigatures == 0 - - def test_antiAliasMethod(self): - assert self.text_item.antiAliasMethod == 3 - - def test_autoKerning(self): - assert self.text_item.autoKerning == 2 - - def test_autoLeadingAmount(self): - assert self.text_item.autoLeadingAmount == 120.00000476837158 - - def test_set_autoLeadingAmount(self): - self.text_item.autoLeadingAmount = 20 - assert self.text_item.autoLeadingAmount == 20.000000298023224 - - def test_baseline_shift(self): - assert self.text_item.baselineShift == 0.0 - - def test_fauxBold(self): - assert not self.text_item.fauxBold - - def test_set_fauxBold(self): - assert not self.text_item.fauxBold - self.text_item.fauxBold = True - assert self.text_item.fauxBold - - def test_fauxItalic(self): - assert not self.text_item.fauxItalic - - def test_firstLineIndent(self): - assert self.text_item.firstLineIndent == 0.0 - - def test_get_font(self): - assert self.text_item.font == "ArialMT" - - def test_set_font(self): - self.text_item.font = "AdobeThai-Regular" - assert self.text_item.font == "AdobeThai-Regular" - - def test_hangingPunctuation(self): - assert not self.text_item.hangingPunctuation - - def test_hyphenateAfterFirst(self): - assert self.text_item.hyphenateAfterFirst == 2 - - 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 - - def test_kind(self): - assert self.text_item.kind == 1 - - def test_set_kind(self): - self.text_item.kind = TextType.ParagraphText - assert self.text_item.kind == 2 - assert self.text_item.kind == TextType.ParagraphText - - def test_noBreak(self): - assert not self.text_item.noBreak - - def test_position(self): - assert self.text_item.position == (5.0, 57.0) - - def test_size(self): - assert self.text_item.size == 18.0 - - def test_change_size(self): - self.text_item.size = 20 - assert self.text_item.size == 20.0 +"""""" +# Import third-party modules +from __future__ import annotations + +import pytest + +# Import local modules +from photoshop import Session +from photoshop.api.enumerations import TextType + + +class TestTextItem: + """Test the solidColor.""" + + # pylint: disable=attribute-defined-outside-init + @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.run_action() + doc = self.session.active_document + layer = doc.activeLayer + self.text_item = layer.textItem() # -> TextItem + # self.session.close() + + def manual_test_alternateLigatures(self): + assert self.text_item.alternateLigatures == 0 + + def test_antiAliasMethod(self): + assert self.text_item.antiAliasMethod == 3 + + def test_autoKerning(self): + assert self.text_item.autoKerning == 2 + + def test_autoLeadingAmount(self): + assert self.text_item.autoLeadingAmount == 120.00000476837158 + + def test_set_autoLeadingAmount(self): + self.text_item.autoLeadingAmount = 20 + assert self.text_item.autoLeadingAmount == 20.000000298023224 + + def test_baseline_shift(self): + assert self.text_item.baselineShift == 0.0 + + def test_fauxBold(self): + assert not self.text_item.fauxBold + + def test_set_fauxBold(self): + assert not self.text_item.fauxBold + self.text_item.fauxBold = True + assert self.text_item.fauxBold + + def test_fauxItalic(self): + assert not self.text_item.fauxItalic + + def test_firstLineIndent(self): + assert self.text_item.firstLineIndent == 0.0 + + def test_get_font(self): + assert self.text_item.font == "ArialMT" + + def test_set_font(self): + self.text_item.font = "AdobeThai-Regular" + assert self.text_item.font == "AdobeThai-Regular" + + def test_hangingPunctuation(self): + assert not self.text_item.hangingPunctuation + + def test_hyphenateAfterFirst(self): + assert self.text_item.hyphenateAfterFirst == 2 + + 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 + + def test_kind(self): + assert self.text_item.kind == 1 + + def test_set_kind(self): + self.text_item.kind = TextType.ParagraphText + assert self.text_item.kind == 2 + assert self.text_item.kind == TextType.ParagraphText + + def test_noBreak(self): + assert not self.text_item.noBreak + + def test_position(self): + assert self.text_item.position == (5.0, 57.0) + + def test_size(self): + assert self.text_item.size == 18.0 + + def test_change_size(self): + self.text_item.size = 20 + assert self.text_item.size == 20.0 diff --git a/test/test_imports.py b/test/test_imports.py index 1888151f..e3c110e7 100644 --- a/test/test_imports.py +++ b/test/test_imports.py @@ -1,25 +1,24 @@ -"""Import Test.""" - -# Import future modules -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -# Import built-in modules -import importlib -import pkgutil - -# Import local modules -import photoshop - - -def test_imports(): - """Test import modules.""" - prefix = "{}.".format(photoshop.__name__) - iter_packages = pkgutil.walk_packages( - photoshop.__path__, # noqa: WPS609 - prefix, - ) - for _, name, _ in iter_packages: - module_name = name if name.startswith(prefix) else prefix + name - importlib.import_module(module_name) +"""Import Test.""" + +# Import future modules + +# Import built-in modules +from __future__ import annotations + +import importlib +import pkgutil + +# Import local modules +import photoshop + + +def test_imports(): + """Test import modules.""" + prefix = f"{photoshop.__name__}." + iter_packages = pkgutil.walk_packages( + photoshop.__path__, + prefix, + ) + for _, name, _ in iter_packages: + module_name = name if name.startswith(prefix) else prefix + name + importlib.import_module(module_name)