├── .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 |
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 |
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 | [ ] [ ] [ . [ ] [ ] ] [ # [ | ] ] [ +