├── .flake8 ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── critical_bug.md │ └── feature_suggestion.md ├── dependabot.yml └── workflows │ ├── build.yml │ ├── coverage.yml │ ├── coverage_comment.yml │ ├── format_check.yml │ ├── lint.yml │ ├── test.yml │ └── type_check.yml ├── .gitignore ├── .readthedocs.yaml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.md ├── docs ├── Makefile ├── make.bat ├── requirements.txt └── source │ ├── api │ ├── color.rst │ ├── exceptions.rst │ ├── geometry.rst │ ├── image.rst │ ├── index.rst │ ├── padding.rst │ ├── render.rst │ ├── renderable.rst │ ├── toplevel.rst │ ├── utils.rst │ └── widget.rst │ ├── conf.py │ ├── faqs.rst │ ├── glossary.rst │ ├── guide │ ├── concepts.rst │ ├── formatting.rst │ └── index.rst │ ├── index.rst │ ├── issues.rst │ ├── resources │ ├── logo.ico │ ├── logo.png │ └── tutorial │ │ ├── alpha_0_5.png │ │ ├── no_alpha_no_align.png │ │ ├── python.png │ │ ├── scale_set.png │ │ ├── scale_unset.png │ │ ├── str.png │ │ └── white_bg.png │ └── start │ ├── index.rst │ ├── installation.rst │ └── tutorial.rst ├── pyproject.toml ├── requirements.txt ├── src └── term_image │ ├── __init__.py │ ├── _ctlseqs.py │ ├── color.py │ ├── exceptions.py │ ├── geometry.py │ ├── image │ ├── __init__.py │ ├── block.py │ ├── common.py │ ├── iterm2.py │ └── kitty.py │ ├── padding.py │ ├── py.typed │ ├── render │ ├── __init__.py │ └── _iterator.py │ ├── renderable │ ├── __init__.py │ ├── _enum.py │ ├── _exceptions.py │ ├── _renderable.py │ └── _types.py │ ├── utils.py │ └── widget │ ├── __init__.py │ └── _urwid.py └── tests ├── __init__.py ├── images ├── anim.webp ├── elephant.png ├── hori.jpg ├── lion.gif ├── python.png ├── python_sym.png ├── trans.png └── vert.jpg ├── render ├── __init__.py └── test_iterator.py ├── renderable ├── __init__.py ├── test_renderable.py └── test_types.py ├── test_color.py ├── test_geometry.py ├── test_image ├── __init__.py ├── common.py ├── test_base.py ├── test_block.py ├── test_iterm2.py ├── test_kitty.py ├── test_others.py └── test_url.py ├── test_iterator.py ├── test_padding.py ├── test_top_level.py └── widget ├── __init__.py └── urwid ├── __init__.py ├── test_main.py └── test_screen.py /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max_line_length = 88 3 | extend_ignore = E203 4 | extend_exclude = .venv,build 5 | count = True 6 | color = auto 7 | show_source = True 8 | statistics = True 9 | per-file-ignores = 10 | tests/*:F403 11 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: ['https://www.buymeacoffee.com/anonymoux47'] 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Create a report to help us fix a bug 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Description** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 16 | 1. Go to '...' 17 | 2. Click on '....' 18 | 3. Scroll down to '....' 19 | 4. See error 20 | 21 | **Expected behavior** 22 | A clear and concise description of what you expected to happen. 23 | 24 | **Screenshots** 25 | If applicable, add screenshots to help explain your problem. 26 | 27 | **Desktop (please complete the following information, if applicable):** 28 | 29 | - OS: [e.g. Ubuntu] 30 | - Version [e.g. 21.10] 31 | - Kernel version (if applicable) [e.g Linux 5.14] 32 | 33 | **Smartphone (please complete the following information, if applicable):** 34 | 35 | - Device: [e.g. Samsumg S20] 36 | - OS: [e.g. Android] 37 | - Version [e.g. 9 (Pie)] 38 | 39 | **Package info:** 40 | 41 | - Python version: [e.g 3.9.5] 42 | - Package version: [e.g 1.2.3] 43 | - Installation method: [e.g PyPI, from source] 44 | 45 | **Terminal Emulator:** 46 | 47 | - Name: [e.g Gnome Terminal, Kitty, iTerm2] 48 | - Version: 49 | 50 | **Additional context** 51 | Add any other context about the problem here. 52 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/critical_bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Critical Bug 3 | about: Issues that should be fixed with utmost priority 4 | title: '' 5 | labels: critical 6 | assignees: AnonymouX47 7 | 8 | --- 9 | 10 | **Description** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 16 | 1. Go to '...' 17 | 2. Click on '....' 18 | 3. Scroll down to '....' 19 | 4. See error 20 | 21 | **Expected behavior** 22 | A clear and concise description of what you expected to happen. 23 | 24 | **Screenshots** 25 | If applicable, add screenshots to help explain your problem. 26 | 27 | **Desktop (please complete the following information, if applicable):** 28 | 29 | - OS: [e.g. Ubuntu] 30 | - Version [e.g. 21.10] 31 | - Kernel version (if applicable) [e.g Linux 5.14] 32 | 33 | **Smartphone (please complete the following information, if applicable):** 34 | 35 | - Device: [e.g. Samsumg S20] 36 | - OS: [e.g. Android] 37 | - Version [e.g. 9 (Pie)] 38 | 39 | **Package info:** 40 | 41 | - Python version: [e.g 3.9.5] 42 | - Package version: [e.g 1.2.3] 43 | - Installation method: [e.g PyPI, from source] 44 | 45 | **Terminal Emulator:** 46 | 47 | - Name: [e.g Gnome Terminal, Kitty, iTerm2] 48 | - Version: 49 | 50 | **Additional context** 51 | Add any other context about the problem here. 52 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_suggestion.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Suggestion 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature suggestion related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the suggested feature here. 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | commit-message: 8 | prefix: "deps" 9 | 10 | - package-ecosystem: "pip" 11 | directory: "/" 12 | schedule: 13 | interval: "weekly" 14 | commit-message: 15 | prefix: "deps" 16 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build and install the package with a variety of Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: build 5 | 6 | on: 7 | push: 8 | branches: [ main ] 9 | pull_request: 10 | 11 | jobs: 12 | build: 13 | 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | os: [ubuntu-latest, macos-latest, windows-latest] 18 | python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] 19 | 20 | runs-on: ${{ matrix.os }} 21 | 22 | steps: 23 | - uses: actions/checkout@v4 24 | 25 | - name: Set up Python ${{ matrix.python-version }} 26 | uses: actions/setup-python@v5 27 | with: 28 | python-version: ${{ matrix.python-version }} 29 | cache: 'pip' 30 | 31 | - name: Test package build and installation (for non-Windows) 32 | if: matrix.os != 'windows-latest' 33 | run: | 34 | make build 35 | pip install -I dist/*.whl 36 | pip install -I dist/*.tar.gz 37 | 38 | - name: Test package build and installation (for Windows) 39 | if: matrix.os == 'windows-latest' 40 | run: | 41 | make build 42 | pip install -I dist\$(Get-ChildItem -Path dist\*.whl -Name) 43 | pip install -I dist\$(Get-ChildItem -Path dist\*.tar.gz -Name) 44 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | name: coverage 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | 8 | jobs: 9 | coverage: 10 | 11 | name: Run tests and update coverage stats 12 | runs-on: ubuntu-latest 13 | permissions: 14 | # Gives the action the necessary permissions for publishing new 15 | # comments in pull requests. 16 | pull-requests: write 17 | # Gives the action the necessary permissions for pushing data to the 18 | # python-coverage-comment-action branch, and for editing existing 19 | # comments (to avoid publishing multiple comments in the same PR) 20 | contents: write 21 | 22 | steps: 23 | - uses: actions/checkout@v4 24 | 25 | - name: Set up Python 26 | uses: actions/setup-python@v5 27 | with: 28 | python-version: "3.13" 29 | cache: 'pip' 30 | 31 | - name: Install the package and dev dependencies 32 | run: make dev 33 | 34 | - name: Run the tests and produce the .coverage file 35 | run: make test-all-cov 36 | 37 | - name: Coverage comment 38 | id: coverage_comment 39 | uses: py-cov-action/python-coverage-comment-action@v3 40 | with: 41 | GITHUB_TOKEN: ${{ github.token }} 42 | MINIMUM_GREEN: 85 43 | MINIMUM_ORANGE: 75 44 | 45 | - name: Store Pull Request comment to be posted 46 | uses: actions/upload-artifact@v4 47 | if: steps.coverage_comment.outputs.COMMENT_FILE_WRITTEN == 'true' 48 | with: 49 | # If you use a different name, update COMMENT_ARTIFACT_NAME accordingly 50 | name: python-coverage-comment-action 51 | # If you use a different name, update COMMENT_FILENAME accordingly 52 | path: python-coverage-comment-action.txt 53 | -------------------------------------------------------------------------------- /.github/workflows/coverage_comment.yml: -------------------------------------------------------------------------------- 1 | name: coverage comment 2 | 3 | on: 4 | workflow_run: 5 | workflows: ["coverage"] 6 | types: 7 | - completed 8 | 9 | jobs: 10 | coverage-comment: 11 | 12 | name: Post PR coverage comment 13 | runs-on: ubuntu-latest 14 | if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' 15 | permissions: 16 | # Gives the action the necessary permissions for publishing new 17 | # comments in pull requests. 18 | pull-requests: write 19 | # Gives the action the necessary permissions for editing existing 20 | # comments (to avoid publishing multiple comments in the same PR) 21 | contents: write 22 | # Gives the action the necessary permissions for looking up the 23 | # workflow that launched this workflow, and download the related 24 | # artifact that contains the comment to be published 25 | actions: read 26 | 27 | steps: 28 | # DO NOT run actions/checkout here, for security reasons 29 | # For details, refer to https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ 30 | - name: Post comment 31 | uses: py-cov-action/python-coverage-comment-action@v3 32 | with: 33 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 34 | GITHUB_PR_RUN_ID: ${{ github.event.workflow_run.id }} 35 | # Uncomment and update these if you changed the default values: 36 | # COMMENT_ARTIFACT_NAME: python-coverage-comment-action 37 | # COMMENT_FILENAME: python-coverage-comment-action.txt 38 | -------------------------------------------------------------------------------- /.github/workflows/format_check.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies and check formatting with a variety of Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: format check 5 | 6 | on: 7 | push: 8 | branches: [ main ] 9 | pull_request: 10 | 11 | jobs: 12 | format-check: 13 | 14 | runs-on: ubuntu-latest 15 | strategy: 16 | fail-fast: false 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - name: Set up Python 3.13 22 | uses: actions/setup-python@v5 23 | with: 24 | python-version: "3.13" 25 | cache: 'pip' 26 | 27 | - name: Install dev dependencies 28 | run: make req 29 | 30 | - name: Check formatting with black 31 | run: make check-format 32 | 33 | - name: Check imports formatting with isort 34 | run: make check-imports 35 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies and lint with a variety of Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: lint 5 | 6 | on: 7 | push: 8 | branches: [ main ] 9 | pull_request: 10 | 11 | jobs: 12 | lint: 13 | 14 | runs-on: ubuntu-latest 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] 19 | 20 | steps: 21 | - uses: actions/checkout@v4 22 | 23 | - name: Set up Python ${{ matrix.python-version }} 24 | uses: actions/setup-python@v5 25 | with: 26 | python-version: ${{ matrix.python-version }} 27 | cache: 'pip' 28 | 29 | - name: Install dev dependencies 30 | run: make req 31 | 32 | - name: Lint with flake8 33 | run: | 34 | # stop the build if there are Python syntax errors or undefined names 35 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 36 | # Extended checks 37 | make lint 38 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install the package and dev dependencies and run tests with a variety of Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: test 5 | 6 | on: 7 | push: 8 | branches: [ main ] 9 | pull_request: 10 | 11 | jobs: 12 | test: 13 | 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | os: [ubuntu-latest, windows-latest] 18 | python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] 19 | 20 | runs-on: ${{ matrix.os }} 21 | 22 | steps: 23 | - uses: actions/checkout@v4 24 | 25 | - name: Set up Python ${{ matrix.python-version }} 26 | uses: actions/setup-python@v5 27 | with: 28 | python-version: ${{ matrix.python-version }} 29 | cache: 'pip' 30 | 31 | - name: Install the package and dev dependencies 32 | run: make dev 33 | 34 | - name: Test with pytest 35 | run: make test-all 36 | -------------------------------------------------------------------------------- /.github/workflows/type_check.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies and type-check across a variety of platforms and Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: type check 5 | 6 | on: 7 | push: 8 | branches: [ main ] 9 | pull_request: 10 | 11 | jobs: 12 | type-check: 13 | 14 | runs-on: ubuntu-latest 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | platform: ["linux", "darwin"] 19 | python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] 20 | 21 | steps: 22 | - uses: actions/checkout@v4 23 | 24 | - name: Set up Python 3.13 25 | uses: actions/setup-python@v5 26 | with: 27 | python-version: "3.13" 28 | cache: 'pip' 29 | 30 | - name: Install dev dependencies 31 | run: make req 32 | 33 | - name: Type-check with mypy 34 | run: mypy --platform ${{ matrix.platform }} --python-version ${{ matrix.python-version }} 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # NOTE: 2 | # Only entries directly pertaining to the project should be added here. 3 | # Anything specific to your development process, environemnt or IDE should be ignored locally 4 | # i.e from "$GIT_DIR/info/exclude", "~/.config/git/ignore" or "$XDG_CONFIG_HOME/git/ignore", etc. 5 | # It helps to keep things simple, thanks. 6 | 7 | # Code coverage data 8 | .coverage 9 | 10 | # Python module cache 11 | __pycache__/ 12 | 13 | # Distribution/Packaging 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | sdist/ 21 | wheels/ 22 | *.egg-info/ 23 | *.egg 24 | MANIFEST 25 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # Read the Docs configuration file 2 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 3 | 4 | # Required 5 | version: 2 6 | 7 | # Set the version of Python and other tools you might need 8 | build: 9 | os: ubuntu-20.04 10 | tools: 11 | python: "3.13" 12 | 13 | # Build documentation in the docs/ directory with Sphinx 14 | sphinx: 15 | configuration: docs/source/conf.py 16 | fail_on_warning: true 17 | 18 | formats: 19 | - htmlzip 20 | 21 | # Optionally declare the Python requirements required to build your docs 22 | python: 23 | install: 24 | # For `autodoc` 25 | - method: pip 26 | path: . 27 | - requirements: docs/requirements.txt 28 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributor Covenant Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | We as members, contributors, and leaders pledge to make participation in our 7 | community a harassment-free experience for everyone, regardless of age, body 8 | size, visible or invisible disability, ethnicity, sex characteristics, gender 9 | identity and expression, level of experience, education, socio-economic status, 10 | nationality, personal appearance, race, caste, color, religion, or sexual 11 | identity and orientation. 12 | 13 | We pledge to act and interact in ways that contribute to an open, welcoming, 14 | diverse, inclusive, and healthy community. 15 | 16 | ## Our Standards 17 | 18 | Examples of behavior that contributes to a positive environment for our 19 | community include: 20 | 21 | * Demonstrating empathy and kindness toward other people 22 | * Being respectful of differing opinions, viewpoints, and experiences 23 | * Giving and gracefully accepting constructive feedback 24 | * Accepting responsibility and apologizing to those affected by our mistakes, 25 | and learning from the experience 26 | * Focusing on what is best not just for us as individuals, but for the overall 27 | community 28 | 29 | Examples of unacceptable behavior include: 30 | 31 | * The use of sexualized language or imagery, and sexual attention or advances of 32 | any kind 33 | * Trolling, insulting or derogatory comments, and personal or political attacks 34 | * Public or private harassment 35 | * Publishing others' private information, such as a physical or email address, 36 | without their explicit permission 37 | * Other conduct which could reasonably be considered inappropriate in a 38 | professional setting 39 | 40 | ## Enforcement Responsibilities 41 | 42 | Community leaders are responsible for clarifying and enforcing our standards of 43 | acceptable behavior and will take appropriate and fair corrective action in 44 | response to any behavior that they deem inappropriate, threatening, offensive, 45 | or harmful. 46 | 47 | Community leaders have the right and responsibility to remove, edit, or reject 48 | comments, commits, code, wiki edits, issues, and other contributions that are 49 | not aligned to this Code of Conduct, and will communicate reasons for moderation 50 | decisions when appropriate. 51 | 52 | ## Scope 53 | 54 | This Code of Conduct applies within all community spaces, and also applies when 55 | an individual is officially representing the community in public spaces. 56 | Examples of representing our community include using an official e-mail address, 57 | posting via an official social media account, or acting as an appointed 58 | representative at an online or offline event. 59 | 60 | ## Enforcement 61 | 62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 63 | reported to the community leaders responsible for enforcement at 64 | [INSERT CONTACT METHOD]. 65 | All complaints will be reviewed and investigated promptly and fairly. 66 | 67 | All community leaders are obligated to respect the privacy and security of the 68 | reporter of any incident. 69 | 70 | ## Enforcement Guidelines 71 | 72 | Community leaders will follow these Community Impact Guidelines in determining 73 | the consequences for any action they deem in violation of this Code of Conduct: 74 | 75 | ### 1. Correction 76 | 77 | **Community Impact**: Use of inappropriate language or other behavior deemed 78 | unprofessional or unwelcome in the community. 79 | 80 | **Consequence**: A private, written warning from community leaders, providing 81 | clarity around the nature of the violation and an explanation of why the 82 | behavior was inappropriate. A public apology may be requested. 83 | 84 | ### 2. Warning 85 | 86 | **Community Impact**: A violation through a single incident or series of 87 | actions. 88 | 89 | **Consequence**: A warning with consequences for continued behavior. No 90 | interaction with the people involved, including unsolicited interaction with 91 | those enforcing the Code of Conduct, for a specified period of time. This 92 | includes avoiding interactions in community spaces as well as external channels 93 | like social media. Violating these terms may lead to a temporary or permanent 94 | ban. 95 | 96 | ### 3. Temporary Ban 97 | 98 | **Community Impact**: A serious violation of community standards, including 99 | sustained inappropriate behavior. 100 | 101 | **Consequence**: A temporary ban from any sort of interaction or public 102 | communication with the community for a specified period of time. No public or 103 | private interaction with the people involved, including unsolicited interaction 104 | with those enforcing the Code of Conduct, is allowed during this period. 105 | Violating these terms may lead to a permanent ban. 106 | 107 | ### 4. Permanent Ban 108 | 109 | **Community Impact**: Demonstrating a pattern of violation of community 110 | standards, including sustained inappropriate behavior, harassment of an 111 | individual, or aggression toward or disparagement of classes of individuals. 112 | 113 | **Consequence**: A permanent ban from any sort of public interaction within the 114 | community. 115 | 116 | ## Attribution 117 | 118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 119 | version 2.1, available at 120 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. 121 | 122 | Community Impact Guidelines were inspired by 123 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 124 | 125 | For answers to common questions about this code of conduct, see the FAQ at 126 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at 127 | [https://www.contributor-covenant.org/translations][translations]. 128 | 129 | [homepage]: https://www.contributor-covenant.org 130 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 131 | [Mozilla CoC]: https://github.com/mozilla/diversity 132 | [FAQ]: https://www.contributor-covenant.org/faq 133 | [translations]: https://www.contributor-covenant.org/translations 134 | 135 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Toluwaleke Ogundipe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include src/term_image/py.typed 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build docs 2 | 3 | _: check test 4 | 5 | 6 | # Development Environment Setup 7 | 8 | pip: # Upgrade pip 9 | python -m pip install --upgrade pip 10 | 11 | # # [Un]Install Package 12 | 13 | install: pip # Install package 14 | python -m pip install . 15 | 16 | install-dev: pip # Install package in develop/editable mode 17 | python -m pip install -e . 18 | 19 | uninstall: pip # Uninstall package 20 | python -m pip uninstall --yes term-image 21 | 22 | # # Install Dev/Doc Dependencies 23 | 24 | req: pip # Install dev dependencies 25 | python -m pip install --upgrade -r requirements.txt 26 | 27 | req-doc: pip # Install doc dependencies 28 | python -m pip install --upgrade -r docs/requirements.txt 29 | 30 | req-all: req req-doc 31 | 32 | # # Install Dev/Doc Dependencies and Package 33 | 34 | dev: req install-dev 35 | 36 | dev-doc: req-doc install-dev 37 | 38 | dev-all: req-all install-dev 39 | 40 | 41 | # Pre-commit Checks and Corrections 42 | 43 | check: check-code 44 | 45 | py_files := src/ docs/source/conf.py tests/ 46 | 47 | ## Code Checks 48 | 49 | check-code: lint type check-format check-imports 50 | 51 | lint: 52 | flake8 $(py_files) && echo 53 | 54 | type: 55 | mypy && echo 56 | 57 | check-format: 58 | black --check --diff --color $(py_files) && echo 59 | 60 | check-imports: 61 | isort --check --diff --color $(py_files) && echo 62 | 63 | ## Code Corrections 64 | 65 | format: 66 | black $(py_files) 67 | 68 | imports: 69 | isort $(py_files) 70 | 71 | 72 | # Tests 73 | 74 | ## Filepath variables 75 | 76 | test-top-level := tests/test_top_level.py 77 | test-color := tests/test_color.py 78 | test-geometry := tests/test_geometry.py 79 | test-padding := tests/test_padding.py 80 | test-renderable-renderable := tests/renderable/test_renderable.py 81 | test-renderable-types := tests/renderable/test_types.py 82 | test-render-iterator := tests/render/test_iterator.py 83 | test-base := tests/test_image/test_base.py 84 | test-block := tests/test_image/test_block.py 85 | test-kitty := tests/test_image/test_kitty.py 86 | test-iterm2 := tests/test_image/test_iterm2.py 87 | test-url := tests/test_image/test_url.py 88 | test-others := tests/test_image/test_others.py 89 | test-iterator := tests/test_iterator.py 90 | test-widget-urwid-main := tests/widget/urwid/test_main.py 91 | test-widget-urwid-screen := tests/widget/urwid/test_screen.py 92 | 93 | test-renderable := $(test-renderable-renderable) $(test-renderable-types) 94 | test-render := $(test-render-iterator) 95 | test-text := $(test-block) 96 | test-graphics := $(test-kitty) $(test-iterm2) 97 | test-image := $(test-base) $(test-text) $(test-graphics) $(test-others) 98 | test-widget-urwid := $(test-widget-urwid-main) $(test-widget-urwid-screen) 99 | test-widget := $(test-widget-urwid) 100 | test := $(test-top-level) $(test-color) $(test-geometry) $(test-padding) $(test-renderable) $(test-render) $(test-image) $(test-iterator) $(test-widget) 101 | test-all := $(test) $(test-url) 102 | 103 | ## Targets 104 | 105 | test-top-level \ 106 | test-color \ 107 | test-geometry \ 108 | test-padding \ 109 | test-renderable test-renderable-renderable test-renderable-types \ 110 | test-render test-render-iterator \ 111 | test-image test-base test-text test-graphics test-block test-kitty test-iterm2 test-url test-others test-iterator \ 112 | test-widget test-widget-urwid test-widget-urwid-main test-widget-urwid-screen \ 113 | test test-all: 114 | pytest $($@) 115 | 116 | test-cov: 117 | pytest --cov --cov-append --cov-report=term --cov-report=html $(test) 118 | 119 | test-all-cov: 120 | pytest --cov --cov-report=term --cov-report=html $(test-all) 121 | 122 | test-%-cov: 123 | pytest --cov --cov-append --cov-report=term --cov-report=html $(test-$*) 124 | 125 | 126 | # Building the Docs 127 | 128 | docs: 129 | cd docs/ && make html 130 | 131 | clean-docs: 132 | cd docs/ && make clean 133 | 134 | 135 | # Packaging 136 | 137 | build: pip 138 | python -m pip install --upgrade build 139 | python -m build 140 | 141 | clean: 142 | rm -rf build dist 143 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |

Term-Image

4 | 5 |

6 | 7 |

8 | 9 |

10 | Display images in the terminal with Python 11 |

12 | 13 |

14 | 📖 Docs 15 | ║ 16 | 🏫 Tutorial 17 |

18 | 19 |

20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | Documentation Status 40 | 41 | 42 | 43 | 44 | 45 |

46 | 47 |
48 | 49 | 50 | ## Contents 51 | - [Installation](#installation) 52 | - [Features](#features) 53 | - [Demo](#demo) 54 | - [Quick Start](#library-quick-start) 55 | - [Usage](#usage) 56 | - [Contribution](#contribution) 57 | - [Planned Features](#planned-features) 58 | - [Known Issues](#known-issues) 59 | - [FAQs](#faqs) 60 | - [Credits](#credits) 61 | - [Sponsor This Project](#sponsor-this-project) 62 | 63 | 64 | > ### ⚠️ NOTICE!!! ⚠️ 65 | > The image viewer (CLI and TUI) has been moved to [termvisage]. 66 | 67 | 68 | ## Installation 69 | 70 | ### Requirements 71 | - Operating System: Unix / Linux / Mac OS X / Windows (limited support, see the [FAQs](https://term-image.readthedocs.io/en/stable/faqs.html)) 72 | - [Python](https://www.python.org/) >= 3.9 73 | - A terminal emulator with **any** of the following: 74 | 75 | - support for the [Kitty graphics protocol](https://sw.kovidgoyal.net/kitty/graphics-protocol/). 76 | - support for the [iTerm2 inline image protocol](https://iterm2.com/documentation-images.html). 77 | - full Unicode support and direct-color (truecolor) support 78 | 79 | **Plans to support a wider variety of terminal emulators are in motion** (see [Planned Features](#planned-features)). 80 | 81 | ### Steps 82 | The latest **stable** version can be installed from [PyPI](https://pypi.org/project/term-image) with: 83 | 84 | ```shell 85 | pip install term-image 86 | ``` 87 | 88 | The **development** version can be installed with: 89 | 90 | ```shell 91 | pip install git+https://github.com/AnonymouX47/term-image.git 92 | ``` 93 | 94 | ### Supported Terminal Emulators 95 | See [here](https://term-image.readthedocs.io/en/stable/start/installation.html#supported-terminal-emulators) for a list of tested terminal emulators. 96 | 97 | If you've tested this library on any other terminal emulator that meets the requirements for any of the render styles, 98 | please mention the name (and version) in a new thread under [this discussion](https://github.com/AnonymouX47/term-image/discussions/4). 99 | 100 | Also, if you have any issue with terminal support, you may report or check information about it in the discussion linked above. 101 | 102 | 103 | ## Features 104 | - Multiple image formats (basically all formats supported by [`PIL.Image.open()`](https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html)) 105 | - Multiple image source types: PIL image instance, local file, URL 106 | - Multiple image render styles (with automatic support detection) 107 | - Support for multiple terminal graphics protocols: [Kitty](https://sw.kovidgoyal.net/kitty/graphics-protocol/), [iTerm2](https://iterm2.com/documentation-images.html) 108 | - Exposes various features of the protocols 109 | - Transparency support (with multiple options) 110 | - Animated image support (including transparent ones) 111 | - Multiple formats: GIF, WEBP, APNG (and possibly more) 112 | - Fully controllable iteration over rendered frames of animated images 113 | - Image animation with multiple parameters 114 | - Integration into various TUI / terminal-based output libraries. 115 | - Terminal size awareness 116 | - Automatic and manual image sizing 117 | - Horizontal and vertical alignment 118 | - Automatic and manual font ratio adjustment (to preserve image aspect ratio) 119 | - and more... 😁 120 | 121 | 122 | ## Demo 123 | 124 | Check out this [image viewer][termvisage] based on this library. 125 | 126 | 127 | ## Quick Start 128 | 129 | ### Creating an instance 130 | 131 | 1. Initialize with a file path: 132 | ```python 133 | from term_image.image import from_file 134 | 135 | image = from_file("path/to/image.png") 136 | ``` 137 | 138 | 2. Initialize with a URL: 139 | ```python 140 | from term_image.image import from_url 141 | 142 | image = from_url("https://www.example.com/image.png") 143 | ``` 144 | 145 | 3. Initialize with a PIL (Pillow) image instance: 146 | ```python 147 | from PIL import Image 148 | from term_image.image import AutoImage 149 | 150 | img = Image.open("path/to/image.png") 151 | image = AutoImage(img) 152 | ``` 153 | 154 | ### Drawing/Displaying an Image 155 | 156 | There are two basic ways to draw an image to the terminal screen: 157 | 158 | 1. Using the `draw()` method: 159 | ```python 160 | image.draw() 161 | ``` 162 | **NOTE:** `draw()` has various parameters for render formatting. 163 | 164 | 2. Using `print()` with an image render output: 165 | ```python 166 | print(image) # without formatting 167 | # OR 168 | print(f"{image:>200.^100#ffffff}") # with formatting 169 | ``` 170 | 171 | For animated images, only the former animates the output, the latter only draws the current frame. 172 | 173 | See the [tutorial](https://term-image.readthedocs.io/en/stable/start/tutorial.html) for a more detailed introduction. 174 | 175 | 176 | ## Usage 177 | 178 |

179 | 🚧 Under Construction - There will most likely be incompatible changes between minor versions of 180 | version zero! 181 |

182 | 183 | **If you want to use this library in a project while it's still on version zero, ensure you pin the dependency to a specific minor version e.g `>=0.4,<0.5`.** 184 | 185 | See the [docs](https://term-image.readthedocs.io) for the User Guide and API Reference. 186 | 187 | 188 | ## Contribution 189 | 190 | Please read through the [guidelines](https://github.com/AnonymouX47/term-image/blob/main/CONTRIBUTING.md). 191 | 192 | For code contributions, you should also check out the [Planned Features](#planned-features). 193 | If you wish to work on any of the listed features/improvements, please click on the linked issue or go through the [issues](https://github.com/AnonymouX47/term-image/issues) section and join in on an ongoing discussion about the task or create a new issue if one hasn't been created yet, so that the implementation can be discussed. 194 | 195 | Hint: You can filter issues by *label* or simply *search* using the features's description. 196 | 197 | Thanks! 💓 198 | 199 | 200 | ## Planned Features 201 | 202 | See the [milestones](https://github.com/AnonymouX47/term-image/milestones) and [open issues with the `planned` label](https://github.com/AnonymouX47/term-image/issues?q=label%3A%22planned%22+is%3Aopen). 203 | 204 | ## Known Issues 205 | 206 | See [here](https://term-image.readthedocs.io/en/stable/issues.html). 207 | 208 | ## FAQs 209 | 210 | See the [FAQs](https://term-image.readthedocs.io/en/stable/faqs.html) section of the docs. 211 | 212 | ## Credits 213 | 214 | The following projects have been (and are still) crucial to the development of this project: 215 | - [Pillow](https://python-pillow.org) by [Fredrik Lundh, Jeffrey A. Clark (Alex) and contributors](https://github.com/python-pillow/Pillow/graphs/contributors) 216 | - [Requests](https://requests.readthedocs.io) by [Kenneth Reitz and others](https://requests.readthedocs.io/en/latest/dev/authors/) 217 | 218 | The logo was composed using resource(s) from the following source(s): 219 | - [Gallery icons created by Andrean Prabowo - Flaticon](https://www.flaticon.com/free-icons/gallery) 220 | 221 | This project started as [img](https://github.com/lainq/img) by Pranav Baburaj (but the author had no intentions to take it any further). 222 | 223 | ## Sponsor This Project 224 | 225 | 226 | Buy Me A Coffee 227 | 228 | 229 | Any amount will go a long way in aiding the progress and development of this project. 230 | Thank you! 💓 231 | 232 | 233 | [termvisage]: https://github.com/AnonymouX47/termvisage 234 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.https://www.sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | furo==2024.8.6 2 | sphinx==8.1.3 3 | sphinx-toolbox==3.9.0 4 | sphinxcontrib-prettyspecialmethods==0.1.0 5 | urwid==2.6.16 6 | -------------------------------------------------------------------------------- /docs/source/api/color.rst: -------------------------------------------------------------------------------- 1 | ``color`` Module 2 | =================== 3 | 4 | .. automodule:: term_image.color 5 | -------------------------------------------------------------------------------- /docs/source/api/exceptions.rst: -------------------------------------------------------------------------------- 1 | ``exceptions`` Module 2 | ===================== 3 | 4 | .. automodule:: term_image.exceptions 5 | -------------------------------------------------------------------------------- /docs/source/api/geometry.rst: -------------------------------------------------------------------------------- 1 | ``geometry`` Module 2 | =================== 3 | 4 | .. automodule:: term_image.geometry 5 | -------------------------------------------------------------------------------- /docs/source/api/image.rst: -------------------------------------------------------------------------------- 1 | ``image`` Module 2 | ================ 3 | 4 | .. module:: term_image.image 5 | 6 | Functions 7 | --------- 8 | 9 | These functions automatically detect the best supported render style for the 10 | current terminal. 11 | 12 | Since all classes share a common interface (as defined by :py:class:`BaseImage`), 13 | any operation supported by one image class can be performed on any other image class, 14 | except style-specific operations. 15 | 16 | .. automodulesumm:: term_image.image 17 | :autosummary-sections: Functions 18 | :autosummary-no-titles: 19 | 20 | 21 | .. autofunction:: auto_image_class 22 | 23 | .. autofunction:: AutoImage 24 | 25 | .. autofunction:: from_file 26 | 27 | .. autofunction:: from_url 28 | 29 | 30 | Enumerations 31 | ------------ 32 | 33 | .. automodulesumm:: term_image.image 34 | :autosummary-sections: Enumerations 35 | :autosummary-no-titles: 36 | 37 | 38 | .. autoclass:: ImageSource() 39 | :autosummary-sections: None 40 | 41 | | 42 | 43 | .. autoclass:: Size() 44 | :autosummary-sections: None 45 | 46 | 47 | .. _image-classes: 48 | 49 | Image Classes 50 | ------------- 51 | 52 | Class Hierarchy 53 | ^^^^^^^^^^^^^^^ 54 | 55 | * :py:class:`BaseImage` 56 | 57 | * :py:class:`TextImage` 58 | 59 | * :py:class:`BlockImage` 60 | 61 | * :py:class:`GraphicsImage` 62 | 63 | * :py:class:`ITerm2Image` 64 | * :py:class:`KittyImage` 65 | 66 | 67 | The Classes 68 | ^^^^^^^^^^^ 69 | 70 | .. automodulesumm:: term_image.image 71 | :autosummary-sections: Classes 72 | :autosummary-no-titles: 73 | :autosummary-exclude-members: ImageIterator 74 | 75 | 76 | .. autoclass:: BaseImage 77 | 78 | | 79 | 80 | .. autoclass:: TextImage 81 | 82 | | 83 | 84 | .. autoclass:: BlockImage 85 | 86 | | 87 | 88 | .. autoclass:: GraphicsImage 89 | 90 | | 91 | 92 | .. autoclass:: ITerm2Image 93 | 94 | | 95 | 96 | .. autoclass:: KittyImage 97 | 98 | | 99 | 100 | .. _context-manager: 101 | 102 | Context Management Protocol Support 103 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 104 | 105 | :py:class:`BaseImage` instances are context managers i.e they can be used with the ``with`` statement as in:: 106 | 107 | with from_url(url) as image: 108 | ... 109 | 110 | Using an instance as a context manager guarantees **instant** object **finalization** 111 | (i.e clean-up/release of resources), especially for instances with URL sources 112 | (see :py:meth:`BaseImage.from_url`). 113 | 114 | | 115 | 116 | Iteration Support 117 | ^^^^^^^^^^^^^^^^^ 118 | 119 | :term:`Animated` images are iterable i.e they can be used with the ``for`` statement (and other means of iteration such as unpacking) as in:: 120 | 121 | for frame in from_file("animated.gif"): 122 | ... 123 | 124 | Subsequent frames of the image are yielded on subsequent iterations. 125 | 126 | .. note:: 127 | - ``iter(anim_image)`` returns an :py:class:`ImageIterator` instance with a repeat count of ``1``, hence caching is disabled. 128 | - The frames are unformatted and transparency is enabled i.e as returned by ``str(image)``. 129 | 130 | For extensive or custom iteration, use :py:class:`ImageIterator` directly. 131 | 132 | 133 | Other Classes 134 | ------------- 135 | 136 | .. autoclass:: ImageIterator 137 | -------------------------------------------------------------------------------- /docs/source/api/index.rst: -------------------------------------------------------------------------------- 1 | API Reference 2 | ============= 3 | 4 | .. attention:: 5 | 🚧 Under Construction - There might be incompatible interface changes between minor 6 | versions of `version zero `_! 7 | 8 | If you want to use the library in a project while it's still on version zero, 9 | ensure you pin the dependency to a specific minor version e.g ``>=0.4,<0.5``. 10 | 11 | On this note, you probably also want to switch to the specific documentation for the 12 | version you're using (somewhere at the lower left corner of this page). 13 | 14 | .. attention:: 15 | Any module or definition not documented here should be considered part of the 16 | private interface and can be changed or removed at any time without notice. 17 | 18 | .. toctree:: 19 | :maxdepth: 2 20 | :caption: Sub-sections: 21 | 22 | toplevel 23 | color 24 | exceptions 25 | geometry 26 | image 27 | padding 28 | renderable 29 | render 30 | utils 31 | widget 32 | -------------------------------------------------------------------------------- /docs/source/api/padding.rst: -------------------------------------------------------------------------------- 1 | ``padding`` Module 2 | ================== 3 | 4 | .. module:: term_image.padding 5 | 6 | Classes 7 | ------- 8 | 9 | .. automodulesumm:: term_image.padding 10 | :autosummary-sections: Classes 11 | :autosummary-no-titles: 12 | 13 | 14 | .. autoclass:: Padding 15 | 16 | | 17 | 18 | .. autoclass:: AlignedPadding 19 | 20 | | 21 | 22 | .. autoclass:: ExactPadding 23 | 24 | | 25 | 26 | 27 | Enumerations 28 | ------------ 29 | 30 | .. automodulesumm:: term_image.padding 31 | :autosummary-sections: Enumerations 32 | :autosummary-no-titles: 33 | 34 | 35 | .. autoclass:: HAlign() 36 | :autosummary-sections: None 37 | 38 | | 39 | 40 | .. autoclass:: VAlign() 41 | :autosummary-sections: None 42 | 43 | | 44 | 45 | 46 | Exceptions 47 | ---------- 48 | 49 | .. automodulesumm:: term_image.padding 50 | :autosummary-sections: Exceptions 51 | :autosummary-no-titles: 52 | 53 | 54 | .. autoexception:: PaddingError 55 | .. autoexception:: RelativePaddingDimensionError 56 | 57 | | 58 | 59 | 60 | Extension API 61 | ------------- 62 | 63 | .. note:: 64 | The following definitions are provided and required only for extended use of the 65 | interfaces defined above. 66 | 67 | Everything required for normal usage should typically be exposed in the public API. 68 | 69 | .. _padding-ext-api: 70 | 71 | Padding 72 | ^^^^^^^ 73 | .. class:: Padding 74 | :noindex: 75 | 76 | See :py:class:`Padding` for the public API. 77 | 78 | .. note:: 79 | Instances of subclasses should be immutable to avoid inconsistent results in 80 | asynchronous render operations that perform padding. 81 | 82 | .. autoclasssumm:: Padding 83 | :autosummary-members: _get_exact_dimensions_ 84 | 85 | .. automethod:: _get_exact_dimensions_ 86 | -------------------------------------------------------------------------------- /docs/source/api/render.rst: -------------------------------------------------------------------------------- 1 | ``render`` Module 2 | ================= 3 | 4 | .. module:: term_image.render 5 | 6 | Classes 7 | ------- 8 | 9 | .. automodulesumm:: term_image.render 10 | :autosummary-sections: Classes 11 | :autosummary-no-titles: 12 | 13 | 14 | .. autoclass:: RenderIterator 15 | 16 | | 17 | 18 | 19 | Exceptions 20 | ---------- 21 | 22 | .. automodulesumm:: term_image.render 23 | :autosummary-sections: Exceptions 24 | :autosummary-no-titles: 25 | 26 | 27 | .. autoexception:: RenderIteratorError 28 | .. autoexception:: FinalizedIteratorError 29 | .. autoexception:: StopDefiniteIterationError 30 | 31 | | 32 | 33 | 34 | Extension API 35 | ------------- 36 | 37 | .. note:: 38 | The following definitions are provided and required only for extended use of the 39 | interfaces defined above. 40 | 41 | Everything required for normal usage should typically be exposed in the public API. 42 | 43 | .. _render-iterator-ext-api: 44 | 45 | RenderIterator 46 | ^^^^^^^^^^^^^^ 47 | .. class:: RenderIterator 48 | :noindex: 49 | 50 | See :py:class:`RenderIterator` for the public API. 51 | 52 | .. autoclasssumm:: RenderIterator 53 | :autosummary-members: _from_render_data_ 54 | 55 | .. automethod:: _from_render_data_ 56 | 57 | | 58 | 59 | .. rubric:: Footnotes 60 | 61 | .. [#ri-nf] 62 | The frame to be rendered **next** is: 63 | 64 | * the first frame, if no seek operation or render has occurred; 65 | * otherwise, the frame after that which was rendered **last**, if no seek operation 66 | has occurred since the last render; 67 | * otherwise, the frame set by the **latest** seek operation since the last render. 68 | -------------------------------------------------------------------------------- /docs/source/api/renderable.rst: -------------------------------------------------------------------------------- 1 | ``renderable`` Module 2 | ===================== 3 | 4 | .. module:: term_image.renderable 5 | 6 | Classes 7 | ------- 8 | 9 | .. automodulesumm:: term_image.renderable 10 | :autosummary-no-titles: 11 | :autosummary-members: Renderable, RenderArgs, ArgsNamespace, Frame 12 | 13 | 14 | .. autoclass:: Renderable 15 | :no-autosummary: 16 | :special-members: __iter__, __str__ 17 | :exclude-members: Args 18 | 19 | .. autoclasssumm:: Renderable 20 | :autosummary-special-members: __iter__, __str__ 21 | 22 | .. autoattribute:: Args() 23 | :no-value: 24 | 25 | | 26 | 27 | .. autoclass:: RenderArgs 28 | :special-members: __contains__, __eq__, __getitem__, __hash__, __iter__ 29 | :inherited-members: render_cls 30 | 31 | .. rubric:: Footnotes 32 | 33 | .. [#ra-ass] 34 | The associated :term:`render class` of a set of render arguments (an instance of 35 | this class) is *render_cls*, accessible via :py:attr:`render_cls`. 36 | .. [#ra-com] 37 | An set of render arguments (an instance of this class) is compatible with its 38 | associated [#ra-ass]_ :term:`render class` and its subclasses. 39 | 40 | | 41 | 42 | .. autoclass:: ArgsNamespace 43 | :special-members: __eq__, __hash__, __or__, __pos__, __ror__ 44 | :inherited-members: get_render_cls 45 | 46 | .. seealso:: :ref:`args-namespace`. 47 | 48 | .. rubric:: Footnotes 49 | 50 | .. [#an-ass] 51 | A render argument namespace class (**that has fields**), along with its 52 | subclasses and their instances, is associated with the :term:`render class` 53 | that was :ref:`specified ` **at its creation**. 54 | The associated render class is accessible via :py:meth:`get_render_cls`. 55 | .. [#an-com] 56 | A render argument namespace is compatible with its associated [#an-ass]_ 57 | :term:`render class` and the subclasses thereof. 58 | 59 | | 60 | 61 | .. autoclass:: Frame 62 | :special-members: __str__ 63 | 64 | | 65 | 66 | Enumerations 67 | ------------ 68 | 69 | .. automodulesumm:: term_image.renderable 70 | :autosummary-sections: Enumerations 71 | :autosummary-no-titles: 72 | 73 | 74 | .. autoclass:: FrameCount() 75 | :autosummary-sections: None 76 | 77 | | 78 | 79 | .. autoclass:: FrameDuration() 80 | :autosummary-sections: None 81 | 82 | | 83 | 84 | .. autoclass:: Seek() 85 | :autosummary-sections: None 86 | :undoc-members: 87 | 88 | | 89 | 90 | Exceptions 91 | ---------- 92 | 93 | .. automodulesumm:: term_image.renderable 94 | :autosummary-sections: Exceptions 95 | :autosummary-no-titles: 96 | 97 | 98 | .. autoexception:: RenderableError 99 | .. autoexception:: IndefiniteSeekError 100 | .. autoexception:: RenderError 101 | .. autoexception:: RenderSizeOutofRangeError 102 | .. autoexception:: RenderArgsDataError 103 | .. autoexception:: RenderArgsError 104 | .. autoexception:: RenderDataError 105 | .. autoexception:: IncompatibleArgsNamespaceError 106 | .. autoexception:: IncompatibleRenderArgsError 107 | .. autoexception:: NoArgsNamespaceError 108 | .. autoexception:: NoDataNamespaceError 109 | .. autoexception:: NonAnimatedRenderableError 110 | .. autoexception:: UnassociatedNamespaceError 111 | .. autoexception:: UninitializedDataFieldError 112 | .. autoexception:: UnknownArgsFieldError 113 | .. autoexception:: UnknownDataFieldError 114 | 115 | | 116 | 117 | Type Variables and Aliases 118 | -------------------------- 119 | 120 | .. autotypevar:: OptionalPaddingT 121 | :no-type: 122 | 123 | | 124 | 125 | Extension API 126 | ------------- 127 | 128 | .. note:: 129 | The following interfaces are provided and required only to extend the Renderable API 130 | i.e to create custom renderables or extend any of those provided by this library. 131 | 132 | Everything required for normal usage should typically be exposed in the public API. 133 | 134 | For performance reasons, all implementations of these interfaces within this library 135 | perform no form of argument validation, except stated otherwise. The same should apply 136 | to any extension or override of these interfaces. All arguments are and should be 137 | expected to be valid. Hence, arguments should be validated beforehand if necessary. 138 | 139 | In the same vein, return values of any of these interfaces will not be validated by 140 | the callers before use. 141 | 142 | .. _renderable-ext-api: 143 | 144 | Renderable 145 | ^^^^^^^^^^ 146 | 147 | .. py:class:: Renderable 148 | :noindex: 149 | 150 | See :py:class:`Renderable` for the public API. 151 | 152 | .. autoclasssumm:: Renderable 153 | :autosummary-members: 154 | _Data_, 155 | _EXPORTED_ATTRS_, 156 | _EXPORTED_DESCENDANT_ATTRS_, 157 | _animate_, 158 | _clear_frame_, 159 | _finalize_render_data_, 160 | _get_frame_count_, 161 | _get_render_data_, 162 | _handle_interrupted_draw_, 163 | _init_render_, 164 | _render_, 165 | 166 | .. autoattribute:: _Data_() 167 | :no-value: 168 | .. autoattribute:: _EXPORTED_ATTRS_ 169 | .. autoattribute:: _EXPORTED_DESCENDANT_ATTRS_ 170 | .. automethod:: _animate_ 171 | .. automethod:: _clear_frame_ 172 | .. automethod:: _finalize_render_data_ 173 | .. automethod:: _get_frame_count_ 174 | .. automethod:: _get_render_data_ 175 | .. automethod:: _get_render_size_ 176 | .. automethod:: _handle_interrupted_draw_ 177 | .. automethod:: _init_render_ 178 | .. automethod:: _render_ 179 | 180 | | 181 | 182 | .. _args-namespace: 183 | 184 | ArgsNamespace 185 | ^^^^^^^^^^^^^^^^^^^^ 186 | 187 | .. py:class:: ArgsNamespace 188 | :noindex: 189 | 190 | See :py:class:`~term_image.renderable.ArgsNamespace` for the public API. 191 | 192 | 193 | Defining Fields 194 | """"""""""""""" 195 | 196 | Fields are defined as **annotated** class attributes. All annotated attributes of a 197 | subclass are taken to be fields. Every such attribute must be assigned a value which 198 | is taken to be the default value of the field. 199 | 200 | .. collapse:: Example 201 | 202 | >>> class Foo(Renderable): 203 | ... pass 204 | ... 205 | >>> class FooArgs(ArgsNamespace, render_cls=Foo): 206 | ... foo: str = "FOO" 207 | ... bar: str = "BAR" 208 | ... 209 | >>> FooArgs.get_fields() 210 | mappingproxy({'foo': 'FOO', 'bar': 'BAR'}) 211 | 212 | The attribute annotations are only used to identify the fields, they're never 213 | evaluated or used otherwise by any part of the Renderable API. 214 | The field names will be unbound from their assigned values (the default field 215 | values) during the creation of the class. 216 | 217 | .. note:: 218 | 219 | A subclass that :ref:`inherits ` fields must not define fields. 220 | 221 | 222 | .. _associating-namespace: 223 | 224 | Associating With a Render Class 225 | """"""""""""""""""""""""""""""" 226 | 227 | To associate a namespace class with a render class, the render class should be 228 | specified via the *render_cls* keyword argument in the class definition header. 229 | 230 | .. collapse:: Example 231 | 232 | >>> class Foo(Renderable): 233 | ... pass 234 | ... 235 | >>> class FooArgs(ArgsNamespace, render_cls=Foo): 236 | ... foo: str = "FOO" 237 | ... 238 | >>> FooArgs.get_render_cls() is Foo 239 | True 240 | 241 | .. note:: 242 | 243 | * A subclass that **has fields** must be associated [#an-ass]_ with a render class. 244 | * A subclass that **has NO fields** cannot be associated with a render class. 245 | * A subclass that :ref:`inherits ` fields cannot be 246 | reassociated with another render class. 247 | 248 | .. attention:: 249 | 250 | Due to the design of the Renderable API, if a render class is intended to have 251 | a namespace class asssociated, the namespace class should be associated with it 252 | before it is subclassed or any :py:class:`~term_image.renderable.RenderArgs` 253 | instance associated with it is created. 254 | 255 | 256 | .. _inheriting-fields: 257 | 258 | Inheriting Fields 259 | """"""""""""""""" 260 | 261 | Fields are inherited from any associated [#an-ass]_ render argument namespace class 262 | (i.e anyone that **has fields**) by subclassing it. The new subclass inherits both 263 | the fields and associated render class of its parent. 264 | 265 | .. collapse:: Example 266 | 267 | >>> class Foo(Renderable): 268 | ... pass 269 | ... 270 | >>> class FooArgs(ArgsNamespace, render_cls=Foo): 271 | ... foo: str = "FOO" 272 | ... 273 | >>> FooArgs.get_render_cls() is Foo 274 | True 275 | >>> FooArgs.get_fields() 276 | mappingproxy({'foo': 'FOO'}) 277 | >>> 278 | >>> class SubFooArgs(FooArgs): 279 | ... pass 280 | ... 281 | >>> SubFooArgs.get_render_cls() is Foo 282 | True 283 | >>> SubFooArgs.get_fields() 284 | mappingproxy({'foo': 'FOO'}) 285 | 286 | .. note:: 287 | 288 | A subclass that inherits fields: 289 | 290 | * must not define fields. 291 | * cannot be reassociated with another render class. 292 | 293 | 294 | Other Notes 295 | """"""""""" 296 | 297 | .. note:: 298 | 299 | * A subclass cannot have multiple base classes. 300 | * The constructor of any subclass that **has fields** must not have required 301 | parameters. 302 | 303 | .. tip:: 304 | 305 | A subclass may neither define nor inherit fields. Such can be used as a base 306 | class for other namespace classes. 307 | 308 | .. important:: 309 | 310 | Due to the design and implementation of the API, field values (including defaults) 311 | should: 312 | 313 | * (and are **expected** to) be **immutable**. 314 | 315 | Otherwise, such may yield unexpected behaviour during render operations or 316 | unexpected render outputs, **if an object used as a field value is modified**, 317 | as: 318 | 319 | * a namespace containing a mutable field value (or a set of render arguments 320 | containing such a namespace) may be in use in an asynchronous render 321 | operation, 322 | * different sets of render arguments may contain the same namespace, and 323 | * different namespaces may contain the same object as field values. 324 | 325 | * be **hashable**. 326 | 327 | Otherwise, the namespace and any containing set of render arguments will also 328 | not be hashable. 329 | 330 | | 331 | 332 | Other Classes 333 | ^^^^^^^^^^^^^ 334 | 335 | .. automodulesumm:: term_image.renderable 336 | :autosummary-no-titles: 337 | :autosummary-members: RenderData, DataNamespace, RenderableData 338 | 339 | 340 | .. autoclass:: RenderData 341 | :special-members: __getitem__, __iter__ 342 | :inherited-members: render_cls 343 | 344 | .. rubric:: Footnotes 345 | 346 | .. [#rd-ass] 347 | The associated :term:`render class` of a set of render data (an instance of this 348 | class) is *render_cls*, accessible via :py:attr:`render_cls`. 349 | 350 | | 351 | 352 | .. autoclass:: DataNamespace 353 | :inherited-members: get_render_cls 354 | 355 | .. rubric:: Footnotes 356 | 357 | .. [#dn-ass] 358 | A render data namespace class (**that has fields**), along with its 359 | subclasses and their instances, is associated with the :term:`render class` 360 | that was :ref:`specified ` **at its creation**. 361 | The associated render class is accessible via :py:meth:`get_render_cls`. 362 | 363 | | 364 | 365 | .. autoclass:: RenderableData 366 | -------------------------------------------------------------------------------- /docs/source/api/toplevel.rst: -------------------------------------------------------------------------------- 1 | Top-Level Definitions 2 | ===================== 3 | 4 | .. module:: term_image 5 | 6 | Constants 7 | --------- 8 | 9 | .. autodata:: DEFAULT_QUERY_TIMEOUT 10 | 11 | 12 | Enumerations 13 | ------------ 14 | 15 | .. autoclass:: AutoCellRatio() 16 | 17 | 18 | Functions 19 | --------- 20 | 21 | .. automodulesumm:: term_image 22 | :autosummary-sections: Functions 23 | :autosummary-no-titles: 24 | 25 | .. autofunction:: disable_queries 26 | 27 | .. autofunction:: disable_win_size_swap 28 | 29 | .. autofunction:: enable_queries 30 | 31 | .. autofunction:: enable_win_size_swap 32 | 33 | .. autofunction:: get_cell_ratio 34 | 35 | .. autofunction:: set_cell_ratio 36 | 37 | .. autofunction:: set_query_timeout 38 | -------------------------------------------------------------------------------- /docs/source/api/utils.rst: -------------------------------------------------------------------------------- 1 | ``utils`` Module 2 | ================ 3 | 4 | .. automodule:: term_image.utils 5 | -------------------------------------------------------------------------------- /docs/source/api/widget.rst: -------------------------------------------------------------------------------- 1 | ``widget`` Module 2 | ================= 3 | 4 | .. automodule:: term_image.widget 5 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # For the full list of configuration options, see the documentation: 4 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 5 | 6 | from sphinx.ext.autodoc import DecoratorDocumenter, FunctionDocumenter 7 | from sphinx_toolbox.collapse import CollapseNode 8 | from sphinxcontrib import prettyspecialmethods 9 | 10 | from term_image import __version__, utils 11 | from term_image.image.common import ImageMeta 12 | from term_image.image.iterm2 import ITerm2ImageMeta 13 | 14 | # -- Path setup -------------------------------------------------------------- 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | # sys.path.insert(0, os.path.abspath("../../src")) 20 | 21 | # -- Project information ----------------------------------------------------- 22 | project = "Term-Image" 23 | copyright = "2022, Toluwaleke Ogundipe" 24 | author = "Toluwaleke Ogundipe" 25 | release = __version__ 26 | 27 | # -- General configuration --------------------------------------------------- 28 | extensions = [ 29 | "sphinx.ext.autodoc", 30 | "sphinx.ext.napoleon", 31 | "sphinx.ext.viewcode", 32 | "sphinx.ext.intersphinx", 33 | "sphinx_toolbox.decorators", 34 | "sphinx_toolbox.github", 35 | "sphinx_toolbox.sidebar_links", 36 | "sphinx_toolbox.more_autosummary", 37 | "sphinx_toolbox.collapse", 38 | "sphinx_toolbox.more_autodoc.typevars", 39 | "sphinxcontrib.prettyspecialmethods", 40 | ] 41 | 42 | # -- Warnings ---------------------------------------------------------------- 43 | suppress_warnings = [ 44 | # `autosummary` issues a plethora of this warning. 45 | # See https://github.com/sphinx-doc/sphinx/issues/12589. 46 | # NOTE: Check back later. 47 | "autosummary.import_cycle", 48 | ] 49 | 50 | # -- Options for HTML output ------------------------------------------------- 51 | html_theme = "furo" 52 | html_logo = "resources/logo.png" 53 | html_favicon = "resources/logo.ico" 54 | 55 | # -- Options for extensions ---------------------------------------------- 56 | 57 | # # -- sphinx-autodoc ----------------------------------------------------- 58 | autodoc_default_options = { 59 | "members": True, 60 | "show-inheritance": True, 61 | "member-order": "bysource", 62 | "autosummary": True, 63 | "autosummary-nosignatures": True, 64 | } 65 | autodoc_typehints = "description" 66 | autodoc_typehints_description_target = "documented" 67 | autodoc_member_order = "bysource" 68 | autodoc_inherit_docstrings = False 69 | 70 | # # -- sphinx-intersphinx ---------------------------------------------- 71 | intersphinx_mapping = { 72 | "python": ("https://docs.python.org/3", None), 73 | "pillow": ("https://pillow.readthedocs.io/en/stable", None), 74 | "requests": ("https://requests.readthedocs.io/en/stable", None), 75 | "typing_extensions": ("https://typing-extensions.readthedocs.io/en/stable", None), 76 | "urwid": ("https://urwid.org", None), 77 | } 78 | 79 | # # -- sphinx_toolbox-github ---------------------------------------------- 80 | github_username = "AnonymouX47" 81 | github_repository = "term-image" 82 | 83 | # # -- sphinx_toolbox-more_autosummary ---------------------------------------------- 84 | autodocsumm_member_order = "bysource" 85 | 86 | # -- Event handlers ------------------------------------------------------------- 87 | 88 | # # -- autodocsumm ----------------------------------------------------------- 89 | 90 | 91 | def autodocssumm_grouper(app, what, name, obj, section, parent): 92 | from enum import EnumMeta 93 | from types import FunctionType 94 | 95 | # Documented members with only annotations but no value 96 | # i.e not in the parent's `__dict__` (or `__dir__` ?) 97 | if not name: 98 | # raise Exception(f"{what=}, {obj=}, {section=}, {parent=}") 99 | return 100 | 101 | if isinstance(obj, EnumMeta): 102 | return "Enumerations" 103 | if isinstance(obj, type) and issubclass(obj, Warning): 104 | return "Warnings" 105 | if isinstance(parent, type): 106 | short_name = name.rpartition(".")[2] 107 | # Can't use `getattr()` because of data descriptors that may also be defined 108 | # on the metaclass (such as with `Class[Instance]Property`) 109 | for cls in parent.mro(): 110 | try: 111 | obj = vars(cls)[short_name] 112 | break 113 | except KeyError as e: 114 | err = e 115 | else: 116 | raise err 117 | 118 | if isinstance(obj, utils.ClassProperty): 119 | return "Class Properties" 120 | if isinstance(obj, utils.ClassInstanceProperty): 121 | return "Class/Instance Properties" 122 | if isinstance(obj, property): 123 | return "Instance Properties" 124 | if isinstance(obj, FunctionType): 125 | return ( 126 | "Special Methods" 127 | if name.startswith("__") and name.endswith("__") 128 | else "Instance Methods" 129 | ) 130 | if isinstance(obj, utils.ClassInstanceMethod): 131 | return "Class/Instance Methods" 132 | if isinstance(obj, classmethod): 133 | return "Class Methods" 134 | if isinstance(obj, staticmethod): 135 | return "Static Methods" 136 | if name.startswith("__") and name.endswith("__"): 137 | return "Special Attributes" 138 | 139 | 140 | # -- Extras ----------------------------------------------------------- 141 | 142 | # The properties defined by the metaclass' would be invoked instead of returning the 143 | # property defined by the class 144 | for meta in (ImageMeta, ITerm2ImageMeta): 145 | for attr, value in tuple(vars(meta).items()): 146 | if isinstance(value, utils.ClassPropertyBase): 147 | delattr(meta, attr) 148 | 149 | # # -- autodecorator ------------------------------------------------------------- 150 | 151 | 152 | @classmethod 153 | def can_document_member(cls, member, *args, **kwargs): 154 | return hasattr(member, "_no_redecorate_wrapped_") and ( 155 | super(DecoratorDocumenter, cls).can_document_member(member, *args, **kwargs) 156 | ) 157 | 158 | 159 | DecoratorDocumenter.priority = FunctionDocumenter.priority + 5 160 | DecoratorDocumenter.can_document_member = can_document_member 161 | 162 | # # -- prettyspecialmethods ------------------------------------------------------ 163 | 164 | 165 | def reflected_binary_op_transformer(op): 166 | from sphinxcontrib.prettyspecialmethods import Text, emphasis, inline, patch_node 167 | 168 | def xf(name_node, parameters_node): 169 | return inline( 170 | "", 171 | "", 172 | emphasis("", parameters_node.children[0].astext()), 173 | Text(" "), 174 | patch_node(name_node, op, ()), 175 | Text(" "), 176 | emphasis("", "self"), 177 | ) 178 | 179 | return xf 180 | 181 | 182 | def skip_undoc_special_methods(*args, **kwargs): 183 | pass 184 | 185 | 186 | prettyspecialmethods.SPECIAL_METHODS["__ror__"] = reflected_binary_op_transformer("|") 187 | prettyspecialmethods.show_special_methods = skip_undoc_special_methods 188 | 189 | # # -- sphinx_toolbox.collapse ------------------------------------------------- 190 | 191 | # Fixes some weird `AttributeError` when building on `ReadTheDocs` 192 | CollapseNode.label = None 193 | 194 | # -- Setup Function -------------------------------------------------------------------- 195 | 196 | 197 | def setup(app): 198 | app.connect("autodocsumm-grouper", autodocssumm_grouper) 199 | -------------------------------------------------------------------------------- /docs/source/faqs.rst: -------------------------------------------------------------------------------- 1 | FAQs 2 | ==== 3 | 4 | Why? 5 | - Why not? 6 | - To improve and extend the capabilities of CLI and TUI applications. 7 | - Terminals emulators have always been and always will be! 8 | 9 | What about Windows support? 10 | - `Windows Terminal `_ and 11 | `Mintty `_ (at least) have modern terminal emulator 12 | features including full Unicode and Truecolor support. 13 | - Drawing images and animations doesn't work completely well with Python **for 14 | Windows**. See :doc:`issues`. 15 | - Note that the graphics protocols supported by Mintty would only work for Cygwin, 16 | MSYS and Msys2 programs, or via WSL; not for native Windows programs. 17 | 18 | Why are colours not properly reproduced? 19 | - Some terminal emulators support direct-color (truecolor) sequences but use a 20 | **256-color** palette. This limits color reproduction. 21 | 22 | Why are images out of scale? 23 | - If :ref:`auto-cell-ratio` is supported and enabled, call 24 | :py:func:`~term_image.enable_win_size_swap`. If this doesn't work, 25 | then open an issue `here `_ 26 | with adequate details. 27 | - Otherwise, adjust the :term:`cell ratio` using :py:func:`~term_image.set_cell_ratio`. 28 | 29 | .. _query-timeout-faq: 30 | 31 | Why does my program get garbage input (possibly also written to the screen) or phantom keystrokes? 32 | - This is most definitely due to slow response of the terminal emulator to :ref:`terminal-queries`. 33 | - To resolve this, set a higher timeout using :py:func:`~term_image.set_query_timeout`. The default is :py:data:`~term_image.DEFAULT_QUERY_TIMEOUT` seconds. 34 | - You can also disable terminal queries using :py:func:`~term_image.disable_queries` but note that this disables certain :ref:`features `. 35 | -------------------------------------------------------------------------------- /docs/source/glossary.rst: -------------------------------------------------------------------------------- 1 | Glossary 2 | ======== 3 | 4 | Below are definitions of terms used across the API, exception messages and the documentation. 5 | 6 | .. note:: 7 | 8 | For contributors, some of these terms are also used in the source code, as variable names, in comments, docstrings, etc. 9 | 10 | .. glossary:: 11 | :sorted: 12 | 13 | active terminal 14 | The terminal emulator connected to the first TTY device discovered upon loading 15 | the ``term_image`` package. 16 | 17 | At times, this may also be used to refer to the TTY device itself. 18 | 19 | .. seealso:: :ref:`active-terminal` 20 | 21 | alignment 22 | horizontal alignment 23 | vertical alignment 24 | ... 25 | 26 | alpha threshold 27 | Alpha ratio/value above which a pixel is taken as **opaque** (applies only to :ref:`text-based`). 28 | 29 | .. seealso:: :ref:`transparency` 30 | 31 | animated 32 | Having multiple frames. 33 | 34 | The frames of an animated image are generally meant to be displayed in rapid succession, to give the effect of animation. 35 | 36 | cell ratio 37 | The **aspect ratio** (i.e the ratio of **width to height**) of a **character cell** on a terminal screen. 38 | 39 | .. seealso:: 40 | :py:func:`~term_image.get_cell_ratio` and :py:func:`~term_image.set_cell_ratio` 41 | 42 | frame size 43 | The dimensions of the area used in :term:`automatic sizing`. 44 | 45 | frame width 46 | The width of the area used in :term:`automatic sizing`. 47 | 48 | frame height 49 | The height of the area used in :term:`automatic sizing`. 50 | 51 | render 52 | renders 53 | rendered 54 | rendering 55 | The process of encoding visual/textual data into a byte/character **string** (possibly including terminal control sequences), the result of which is called a :term:`render output`. 56 | 57 | .. seealso:: :py:meth:`~term_image.renderable.Renderable._render_` 58 | 59 | render class 60 | render classes 61 | :py:class:`~term_image.renderable.Renderable` or a subclass of it. 62 | 63 | render output 64 | render outputs 65 | A **string** produced by :term:`rendering` or by padding another render output. 66 | 67 | .. seealso:: :py:attr:`Frame.render_output ` 68 | 69 | render size 70 | rendered size 71 | The amount of space (columns and lines) that'll be occupied by a :term:`render output` **when drawn (written) onto a terminal screen**. 72 | 73 | .. seealso:: :py:attr:`Frame.render_size ` 74 | 75 | render width 76 | rendered width 77 | The amount of **columns** that'll be occupied by a :term:`render output` **when drawn (written) onto a terminal screen**. Also the horizontal component of a :term:`render size`. 78 | 79 | render height 80 | rendered height 81 | The amount of **lines** that'll be occupied by a :term:`render output` **when drawn (written) onto a terminal screen**. Also the vertical component of a :term:`render size`. 82 | 83 | padding 84 | padding size 85 | padding width 86 | padding height 87 | ... 88 | 89 | pixel ratio 90 | The aspect ratio with which one rendered pixel is drawn/displayed on the terminal screen. 91 | 92 | For :ref:`graphics-based`, this is ideally ``1.0``. 93 | 94 | For :ref:`text-based`, this is equivalent to the :term:`cell ratio` multiplied by 2, 95 | since there are technically two times more pixels along the vertical axis than 96 | along the horizontal axis in one character cell. 97 | 98 | render method 99 | render methods 100 | A unique implementation of a :term:`render style`. 101 | 102 | .. seealso:: :ref:`render-methods` 103 | 104 | render style 105 | render styles 106 | style 107 | styles 108 | A specific technique for rendering or displaying pixel data (including images) 109 | in a terminal emulator. 110 | 111 | A render style (or simply *style*) is implemented by a class, often referred to 112 | as a *render style class* (or simply *style class*). 113 | 114 | .. seealso:: :ref:`render-styles` 115 | 116 | manual size 117 | manual sizing 118 | A form of sizing wherein **both** the width and the height are specified to set the image size. 119 | 120 | This form of sizing does not preserve image aspect ratio and can only be used with :term:`fixed sizing`. 121 | 122 | .. seealso:: 123 | :term:`automatic sizing`, 124 | :py:attr:`~term_image.image.BaseImage.size` and 125 | :py:meth:`~term_image.image.BaseImage.set_size` 126 | 127 | automatic size 128 | automatic sizing 129 | A form of sizing wherein an image's size is computed based on a combination of a 130 | :term:`frame size`, the image's original size and a given width **or** height. 131 | 132 | This form of sizing tries to preserve image aspect ratio and can be used with both 133 | :term:`fixed sizing` and :term:`dynamic sizing`. 134 | 135 | .. seealso:: 136 | :term:`manual sizing`, 137 | :py:class:`~term_image.image.Size`, 138 | :py:attr:`~term_image.image.BaseImage.size` and 139 | :py:meth:`~term_image.image.BaseImage.set_size` 140 | 141 | dynamic size 142 | dynamic sizing 143 | A form of sizing wherein the image size is automatically computed at render-time. 144 | 145 | This only works with :term:`automatic sizing`. 146 | 147 | .. seealso:: 148 | :term:`fixed sizing` and 149 | :py:attr:`~term_image.image.BaseImage.size` 150 | 151 | fixed size 152 | fixed sizing 153 | A form of sizing wherein the image size is set to a specific value which won't change until it is re-set. 154 | 155 | This works with both :term:`manual sizing` and :term:`automatic sizing`. 156 | 157 | .. seealso:: 158 | :term:`dynamic sizing`, 159 | :py:meth:`~term_image.image.BaseImage.set_size`, 160 | :py:attr:`~term_image.image.BaseImage.width` and 161 | :py:attr:`~term_image.image.BaseImage.height` 162 | 163 | source 164 | The resource from which an image instance is initialized. 165 | 166 | .. seealso:: 167 | :py:attr:`~term_image.image.BaseImage.source` and 168 | :py:attr:`~term_image.image.BaseImage.source_type` 169 | 170 | terminal size 171 | The amount of columns and lines on a terminal screen at a time i.e without scrolling. 172 | 173 | terminal width 174 | The amount of columns on a terminal screen at a time. 175 | 176 | terminal height 177 | The amount of lines on a terminal screen at a time i.e without scrolling. 178 | 179 | descendant 180 | Refers to an attribute, property or setting set on a class which applies to that 181 | class and all its subclasses on which the attribute, property or setting is unset. 182 | -------------------------------------------------------------------------------- /docs/source/guide/concepts.rst: -------------------------------------------------------------------------------- 1 | Concepts 2 | ======== 3 | 4 | .. _render-styles: 5 | 6 | Render Styles 7 | ------------- 8 | 9 | See :term:`render style`. 10 | 11 | All render style classes are designed to share a common interface (with some having 12 | extensions), making the usage of one class directly compatible with another, except 13 | when using style-specific features. 14 | 15 | Hence, the factory functions :py:class:`~term_image.image.AutoImage`, 16 | :py:class:`~term_image.image.from_file` and :py:class:`~term_image.image.from_url` 17 | provide a means of render-style-agnostic usage of the library. 18 | These functions automatically detect the best render style supported by the :term:`active terminal`. 19 | 20 | There are two main categories of render styles: 21 | 22 | .. _text-based: 23 | 24 | Text-based Render Styles 25 | ^^^^^^^^^^^^^^^^^^^^^^^^ 26 | 27 | Represent images using ASCII or Unicode symbols, and in some cases, with escape sequences to reproduce color. 28 | 29 | Render style classes in this category are subclasses of 30 | :py:class:`~term_image.image.TextImage`. These include: 31 | 32 | * :py:class:`~term_image.image.BlockImage` 33 | 34 | .. _graphics-based: 35 | 36 | Graphics-based Render Styles 37 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 38 | 39 | Represent images with actual pixels, using terminal graphics protocols. 40 | 41 | Render style classes in this category are subclasses of 42 | :py:class:`~term_image.image.GraphicsImage`. These include: 43 | 44 | * :py:class:`~term_image.image.KittyImage` 45 | * :py:class:`~term_image.image.ITerm2Image` 46 | 47 | .. _render-methods: 48 | 49 | Render Methods 50 | ^^^^^^^^^^^^^^ 51 | 52 | A :term:`render style` may implement multiple :term:`render methods`. See the **Render 53 | Methods** section in the description of a render style class (that implements multiple 54 | render methods), for the description of its render methods. 55 | 56 | 57 | .. _auto-cell-ratio: 58 | 59 | Auto Cell Ratio 60 | --------------- 61 | 62 | .. note:: This concerns :ref:`text-based` only. 63 | 64 | The is a feature which when supported, can be used to determine the :term:`cell ratio` 65 | directly from the terminal emulator itself. With this feature, it is possible to always 66 | produce images of text-based render styles with correct **aspect ratio**. 67 | 68 | When using either mode of :py:class:`~term_image.AutoCellRatio`, it's important to 69 | note that some terminal emulators (most non-graphics-capable ones) might have queried. 70 | See :ref:`terminal-queries`. 71 | 72 | If the program will never expect any useful input, particularly **while an image's 73 | size is being set/calculated**, then using :py:attr:`~term_image.AutoCellRatio.DYNAMIC` 74 | mode is OK. For an image with :term:`dynamic size`, this includes when it's being 75 | rendered and when its :py:attr:`~term_image.image.BaseImage.rendered_size`, 76 | :py:attr:`~term_image.image.BaseImage.rendered_width` or 77 | :py:attr:`~term_image.image.BaseImage.rendered_height` property is invoked. 78 | 79 | Otherwise i.e if the program will be expecting input, use 80 | :py:attr:`~term_image.AutoCellRatio.FIXED` mode and use 81 | :py:func:`~term_image.utils.read_tty_all` to read all currently unread input just 82 | before calling :py:func:`~term_image.set_cell_ratio`. 83 | 84 | 85 | .. _active-terminal: 86 | 87 | The Active Terminal 88 | ------------------- 89 | 90 | See :term:`active terminal`. 91 | 92 | The following streams/files are checked in the following order (along with the 93 | rationale behind the ordering): 94 | 95 | * ``STDOUT``: Since it's where images will most likely be drawn. 96 | * ``STDIN``: If output is redirected to a file or pipe and the input is a terminal, 97 | then using it as the :term:`active terminal` should give the expected result i.e the 98 | same as when output is not redirected. 99 | * ``STDERR``: If both output and input are redirected, it's usually unlikely for 100 | errors to be. 101 | * ``/dev/tty``: Finally, if all else fail, fall back to the process' controlling 102 | terminal, if any. 103 | 104 | The first one that is ascertained to be a terminal device is used for all 105 | :ref:`terminal-queries` and to retrieve the terminal (and window) size on some terminal 106 | emulators. 107 | 108 | .. note:: 109 | If none of the streams/files is a TTY device, then a 110 | :py:class:`~term_image.exceptions.TermImageWarning` 111 | is issued and dependent functionality is disabled. 112 | 113 | 114 | .. _terminal-queries: 115 | 116 | Terminal Queries 117 | ---------------- 118 | 119 | Some features of this library require the acquisition of certain information from 120 | the :term:`active terminal`. A single iteration of this acquisition procedure is called a 121 | **query**. 122 | 123 | A query involves three major steps: 124 | 125 | 1. Clear all unread input from the terminal 126 | 2. Write to the terminal 127 | 3. Read from the terminal 128 | 129 | For this procedure to be successful, it must not be interrupted. 130 | 131 | About #1 132 | If the program is expecting input, use :py:func:`~term_image.utils.read_tty_all` 133 | to read all currently unread input (**without blocking**) just before any operation 134 | involving a query. 135 | 136 | About #2 and #3 137 | After sending a request to the terminal, its response is awaited. The default wait 138 | time is :py:data:`~term_image.DEFAULT_QUERY_TIMEOUT` but can be changed 139 | using :py:func:`~term_image.set_query_timeout`. If the terminal emulator 140 | responds after the set timeout, this can result in the application program receiving 141 | what would seem to be garbage or ghost input (see this :ref:`FAQ `). 142 | 143 | If the program includes any other function that could write to the terminal OR 144 | especially, read from the terminal or modify it's attributes, while a query is in 145 | progress (as a result of asynchronous execution e.g multithreading or multiprocessing), 146 | decorate it with :py:func:`~term_image.utils.lock_tty` to ensure it doesn't interfere. 147 | 148 | For example, an :github:repo:`image viewer ` 149 | based on this project uses `urwid `_ which reads from the 150 | terminal using :py:meth:`urwid.raw_display.Screen.get_available_raw_input`. 151 | To prevent this method from interfering with terminal queries, it uses 152 | :py:class:`~term_image.widget.UrwidImageScreen` which overrides and wraps the 153 | method like:: 154 | 155 | class UrwidImageScreen(Screen): 156 | @lock_tty 157 | def get_available_raw_input(self): 158 | return super().get_available_raw_input() 159 | 160 | Also, if the :term:`active terminal` is not the controlling terminal of the process 161 | using this library (e.g output is redirected to another TTY device), ensure no 162 | process that can interfere with a query (e.g a shell or REPL) is currently running 163 | in the active terminal. For instance, such a process can be temporarily put to sleep. 164 | 165 | 166 | .. _queried-features: 167 | 168 | Features that require terminal queries 169 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 170 | 171 | In parentheses are the outcomes when the terminal doesn't support queries or when queries 172 | are disabled. 173 | 174 | - :ref:`auto-cell-ratio` (determined to be unsupported) 175 | - Support checks for :ref:`graphics-based` (determined to be unsupported) 176 | - Auto background color (black is used) 177 | - Alpha blend for pixels above the alpha threshold in transparent renders with 178 | :ref:`text-based` (black is used) 179 | - Workaround for background colors in text-based renders on the Kitty terminal 180 | (the workaround is disabled) 181 | 182 | .. note:: 183 | This list might not always be complete. In case you notice 184 | 185 | - any difference with any unlisted feature when terminal queries are enabled versus 186 | when disabled, or 187 | - a behaviour different from the one specified for the listed features, when terminal 188 | queries are disabled, 189 | 190 | please open an issue `here `_. 191 | -------------------------------------------------------------------------------- /docs/source/guide/formatting.rst: -------------------------------------------------------------------------------- 1 | Render Formatting 2 | ================= 3 | 4 | Render formatting is simply the modification of a primary :term:`render` output. 5 | This is provided via: 6 | 7 | * Python's string formatting protocol by using :py:func:`format`, :py:meth:`str.format` or 8 | `formatted string literals 9 | `_ 10 | with the :ref:`format-spec` 11 | * Parameters of :py:meth:`~term_image.image.BaseImage.draw` 12 | 13 | The following constitute render formatting: 14 | 15 | 16 | .. _padding: 17 | 18 | Padding 19 | ------- 20 | 21 | This adds whitespace around a primary :term:`render` output. 22 | The amount of whitespace added is determined by two values (with respect to the rendered size): 23 | 24 | * :term:`padding width`, determines horizontal padding 25 | 26 | * uses the ``width`` field of the :ref:`format-spec` 27 | * uses the *pad_width* parameter of :py:meth:`~term_image.image.BaseImage.draw` 28 | 29 | * :term:`padding height`, determines vertical padding 30 | 31 | * uses the ``height`` field of the :ref:`format-spec` 32 | * uses the *pad_height* parameter of :py:meth:`~term_image.image.BaseImage.draw` 33 | 34 | If the padding width or height is less than or equal to the width or height of the primary 35 | render output, then the padding has no effect on the corresponding axis. 36 | 37 | 38 | .. _alignment: 39 | 40 | Alignment 41 | --------- 42 | 43 | This determines the position of a primary :term:`render` output within it's :ref:`padding`. 44 | The position is determined by two values: 45 | 46 | * :term:`horizontal alignment`, determines the horizontal position 47 | 48 | * uses the ``h_align`` field of the :ref:`format-spec` 49 | * uses the *h_align* parameter of :py:meth:`~term_image.image.BaseImage.draw` 50 | 51 | * :term:`vertical alignment`, determines the vertical position 52 | 53 | * uses the ``v_align`` field of the :ref:`format-spec` 54 | * uses the *v_align* parameter of :py:meth:`~term_image.image.BaseImage.draw` 55 | 56 | 57 | .. _transparency: 58 | 59 | Transparency 60 | ------------ 61 | 62 | This determines how transparent pixels are rendered. 63 | Transparent pixels can be rendered in one of the following ways: 64 | 65 | * Transparency disabled 66 | 67 | Alpha channel is ignored. 68 | 69 | * uses the ``#`` field of the :ref:`format-spec`, without ``threshold`` or ``bgcolor`` 70 | * uses the *alpha* parameter of :py:meth:`~term_image.image.BaseImage.draw`, set to ``None`` 71 | 72 | * Transparency enabled with an :term:`alpha threshold` 73 | 74 | For :ref:`text-based`, any pixel with an alpha value above the given threshold is 75 | taken as **opaque**. 76 | For :ref:`graphics-based`, the alpha value of each pixel is used as-is. 77 | 78 | * uses the ``threshold`` field of the :ref:`format-spec` 79 | * uses the *alpha* parameter of :py:meth:`~term_image.image.BaseImage.draw`, set to a :py:class:`float` value 80 | 81 | * Transparent pixels overlaid on a color 82 | 83 | May be specified to be a specific color or the default background color of the 84 | terminal emulator (if it can't be determined, black is used). 85 | 86 | * uses the ``bgcolor`` field of the :ref:`format-spec` 87 | * uses the *alpha* parameter of :py:meth:`~term_image.image.BaseImage.draw`, set to a string value 88 | 89 | 90 | .. _format-spec: 91 | 92 | Render Format Specification 93 | --------------------------- 94 | 95 | .. code-block:: none 96 | 97 | [ ] [ ] [ . [ ] [ ] ] [ # [ | ] ] [ +