45 Commits
Author SHA1 Message Date
scofanoandClaude Sonnet 4.6 9f17e718fd chore: update registry description for v1.5.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 12:13:47 -03:00
scofanoandClaude Sonnet 4.6 065adbd8d0 chore: bump version to 1.5.0 for Instagram Question Box release
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 12:10:14 -03:00
scofanoandClaude Sonnet 4.6 028eda753c feat: add Instagram Question Box nodes (image + video)
Two new nodes under Advanced Text Overlay:
- Instagram Question Box (image)
- Instagram Question Box - Video

Features:
- Two-panel sticker layout: dark header + light body with independent
  pixel heights (header_height, body_height)
- Anti-aliased rounded corners via 4x supersampled rendering + LANCZOS
  downscale
- Separate font, size, letter_spacing, fill color/alpha for question
  (header) and answer (body)
- answer_line_spacing and answer_padding controls
- HTML rich text + \n line breaks in answer text (reuses InlineRichTextParser)
- Soft drop-shadow with Gaussian blur
- Same animation system as main nodes (fade, move_from_*, easing, pause)
- Video variant preserves audio via ffmpeg re-mux
- Defaults tuned for 1080-wide portrait stories

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 11:32:25 -03:00
scofano 3cc287ddc7 feat: implement true font name resolution and JSON caching
- Extract internal Font Family Name using fontTools to fix Arial fallback issue.
- Implement JSON caching for system font mapping to improve startup performance.
- Update requirements.txt with fonttools dependency.
- Update README.md with new font resolution features.
- Update .gitignore to exclude font_cache.json.
2026-05-13 12:26:24 -03:00
scofano ea768fa93b update 2026-04-24 14:56:04 -03:00
scofano 1b93cde829 chore: enrich pyproject metadata for ComfyUI registry
Add README, Python version, license, dependencies, and project links
to improve package metadata and registry compatibility. Update the
icon asset and fix its raw GitHub URL to ensure the correct image is
used.chore: enrich pyproject metadata for ComfyUI registry

Add README, Python version, license, dependencies, and project links
to improve package metadata and registry compatibility. Update the
icon asset and fix its raw GitHub URL to ensure the correct image is
used.
2026-04-24 14:55:12 -03:00
scofano 7053dfc72b update 2026-04-24 14:30:44 -03:00
scofano 9afd74f4d6 add icon 2026-04-24 14:27:49 -03:00
scofano 3e9dcbb67f feat: support inline alignment overrides in rich text
Parse align attributes on span/div tags and apply left, center, or right alignment to wrapped line blocks without changing the node-wide font_alignment.

Update the README with new supported tags, examples, and notes so users can understand how inline alignment works and when it starts a new aligned block.feat: support inline alignment overrides in rich text

Parse align attributes on span/div tags and apply left, center, or right alignment to wrapped line blocks without changing the node-wide font_alignment.

Update the README with new supported tags, examples, and notes so users can understand how inline alignment works and when it starts a new aligned block.
2026-03-23 09:15:55 -03:00
scofano ae7a0923f6 feat: add inline rich text styling support to overlays
Support HTML-like inline tags in text overlays for bold, italic,
line breaks, per-segment colors, and highlight backgrounds.

Update text parsing, font selection, measurement, and wrapping logic
so mixed-style segments render accurately while preserving existing
plain-text behavior, and document the new rich text syntax in README.feat: add inline rich text styling support to overlays

Support HTML-like inline tags in text overlays for bold, italic,
line breaks, per-segment colors, and highlight backgrounds.

Update text parsing, font selection, measurement, and wrapping logic
so mixed-style segments render accurately while preserving existing
plain-text behavior, and document the new rich text syntax in README.
2026-03-23 09:06:52 -03:00
scofano ffab79b7fc feat: add font dropdown with system font discovery
- Automatically detects installed fonts on Windows, macOS, and Linux
- Provides cross-platform font scanning with fallback to common fonts
- Adds reusable font system for future nodes
- Updates TextOverlay node to use font dropdown instead of hardcoded font path
2026-02-12 13:46:48 -03:00
scofano 0c64e93180 removed gif
updated requirements
2025-11-24 22:14:54 -03:00
scofano 26f188f0e0 Update README.md 2025-11-24 22:12:25 -03:00
scofano a73d04dfe8 Update README.md 2025-11-24 22:11:50 -03:00
scofano 5d7377287e Update README.md 2025-11-24 22:10:40 -03:00
scofano 95e929863d Update README.md 2025-11-24 22:10:09 -03:00
scofano a0eeebbfbf Update README.md 2025-11-24 22:08:12 -03:00
scofano f93908247d Update README.md 2025-11-24 22:07:48 -03:00
scofano 2e566fc2e8 Update README.md 2025-11-24 22:06:42 -03:00
scofano 93f28874d5 Update README.md 2025-11-24 22:06:01 -03:00
scofano d7708c7cd9 Update README.md 2025-11-24 22:05:03 -03:00
scofano 6d1d321691 Bump version to 1.4.0
Update project version in pyproject.toml from 1.3.0 to 1.4.0 to reflect new changes or improvements.
2025-11-24 22:04:00 -03:00
scofano e6441bbae3 Revise README with expanded feature overview
The README has been updated to provide a clearer, more detailed summary of the module's capabilities, including new sections for features, input parameters, and usage instructions. The documentation now highlights advanced layout, animation, batch processing, and video support, making it easier for users to understand and utilize the node.
2025-11-24 22:03:02 -03:00
scofano 76c82b7af5 Add pause before text overlay animation
Introduces a pause option before starting text overlay for both image and video nodes. The new parameters 'pause_frames_before_start' (frames) and 'pause_seconds_before_start' (seconds) allow delaying the overlay animation, with logic updated to skip overlay during the pause period. Refactors batch and video processing to respect the pause, and updates input types accordingly.
2025-11-24 21:59:22 -03:00
scofano 03b0096be6 Add option to delete original video after processing
Introduces a 'delete_original' boolean parameter to the TextOverlayVideo node. When enabled, the original input video is deleted after processing is complete. This helps manage disk space when processing large numbers of videos.
2025-11-20 16:11:34 -03:00
scofano 86f8686d30 Add audio muxing to processed videos using ffmpeg
After processing a video, the original audio track is now muxed into the output file using ffmpeg if available. This ensures the resulting video retains its original audio, and the process fails gracefully if ffmpeg is not present or an error occurs.
2025-11-20 12:47:52 -03:00
scofano 63253a9267 Update filename prefix and node mapping for video overlay
Changed the default filename prefix for video overlays to 'TxtOver' and updated the node mapping name from 'Advanced Text Overlay Video' to 'Advanced Text Overlay - Video' for consistency.
2025-11-20 11:51:43 -03:00
scofano 28f8d58532 Add Advanced Text Overlay Video node
Introduces the TextOverlayVideo class, enabling text overlay processing on video files with animation support. The new node processes each video frame using the existing text overlay logic, writes the result to a new video file, and provides progress updates via tqdm and ComfyUI's ProgressBar. Also updates the node category and expands NODE_CLASS_MAPPINGS to include the new video node.
2025-11-20 11:45:26 -03:00
scofano 1dd948580c Enhance README with animation types and descriptions
Added detailed descriptions for supported animation types.
2025-10-29 11:33:22 -03:00
scofano a58e0bf17d Update README to use image for Fade_in animation
Replaced Giphy iframe with an image for the Fade_in animation.
2025-10-29 11:30:25 -03:00
scofano 269e3a4f5b Update README to use iframe for Fade_in animation
Replaced static image link with an iframe for the Fade_in animation.
2025-10-29 11:29:20 -03:00
scofano 95dc2adf9a Change animation type formatting in README
Updated animation type representation in README.
2025-10-29 11:28:08 -03:00
scofano b02167cfda Enhance README with Fade_in animation details
Added HTML formatting for Fade_in animation example.
2025-10-29 11:25:27 -03:00
scofano 58f79cf91f Update README with fade_in GIF and remove text
Removed detailed animation descriptions and added a GIF for 'fade_in'.
2025-10-29 11:24:51 -03:00
scofano 89992997c5 Rename animated2.gif to animated.gif 2025-10-29 11:02:01 -03:00
scofano 67d3c6d9d4 Rename animated.gif to animated2.gif 2025-10-29 11:01:44 -03:00
scofano 29e0bbdad6 Bump version to 1.3.0
Update the project version in pyproject.toml from 1.2.0 to 1.3.0 to reflect new changes or features.
2025-10-29 10:59:56 -03:00
scofano dbf6449949 Add animation feature to text overlay node
Introduces a new animation capability for the text overlay node, supporting various animation types (fade, move) and easing modes. The README is updated with detailed documentation on animation parameters, supported types, and technical integration.
2025-10-29 10:58:18 -03:00
scofano 405e4b4d19 Add animation support to TextOverlay node
Introduces a new animations.py module with easing, opacity, and offset helpers. Updates nodes.py to support animated text overlays, including fade and move-in effects, with new parameters for animation kind, duration, easing, and opacity. The node now handles both single images and batches, animating the overlay for a configurable number of frames and holding the final pose for the remainder of the sequence.
2025-10-29 10:54:00 -03:00
scofano 22b183168d Change padding and shift properties to Integer 2025-10-29 08:43:52 -03:00
scofano 6f690c48f7 Merge branch 'main' of https://github.com/scofano/ComfyUI-Advanced-TextOverlay 2025-10-28 12:05:55 -03:00
scofano 824b888222 Parameters order changed 2025-10-28 12:05:51 -03:00
scofano d3d9bc6cff Fix image markdown syntax in README.md 2025-10-28 11:31:37 -03:00
scofano 23a4bb6a79 Update screenshot to be a clickable link 2025-10-28 11:31:04 -03:00
scofano bcfb465380 Added Font Alignment 2025-10-28 11:27:10 -03:00
9 changed files with 2536 additions and 259 deletions
+1
View File
@@ -1 +1,2 @@
/__pycache__
font_cache.json
+416 -69
View File
@@ -6,113 +6,460 @@
**Originally inspired by:** [mikkel/ComfyUI-text-overlay](https://github.com/mikkel/ComfyUI-text-overlay)
This node extends and refines the classic ComfyUI Text Overlay concept with a **re-engineered rendering engine**, **precise alignment logic**, **new customization parameters**, and **enhanced batch handling** — making it the most advanced text overlay solution available for ComfyUI.
This module provides the **most feature‑rich and precise text overlay system available for ComfyUI**, including:
* Completely re‑engineered layout engine
* Pixel‑perfect multiline stroke alignment
* Opacity‑aware backgrounds and shadows
* Inline rich text with HTML-like tags for bold, italic, per-segment text color, and per-segment highlight backgrounds
* Letter/line spacing, padding, and alignment controls
* Full animation engine (fade + directional movement)
* Batch-aware rendering with smart caching
* Full video processing node with audio‑preserving re‑mux
* Font dropdown with system font discovery
- Automatically detects installed fonts on Windows, macOS, and Linux
- Cross-platform font scanning
- Fallback to common font names for compatibility
- Reusable font system for future nodes
* **NEW: Instagram Question Box nodes** — render a two-panel sticker overlay (dark header + light body) matching the Instagram "Ask me a question" story format, with full typography, color, shadow, and animation controls
---
![Advanced Text Overlay Screenshot](https://github.com/scofano/ComfyUI-Advanced-TextOverlay/blob/main/animated.gif)
## ✨ Key Features
## ✨ Key Improvements & New Features
### ✔️ 1. Rebuilt Rendering Engine
### 🧱 1. Rebuilt Rendering Engine
* Accurate bounding boxes for multiline text
* Consistent stroke widths (even‑pixel enforcement)
* Crisp, stable text edges with alpha‑aware compositing
* Fully restructured text rendering logic for higher precision and cleaner anti-aliasing.
* Pixel-accurate bounding box calculations ensure text aligns perfectly to the intended position.
* Consistent stroke behavior across font sizes with improved blending and color stability.
### ✔️ 2. Advanced Layout and Typography
### 🎨 2. Extended Styling and Layout Control
* **Letter spacing** and **line spacing**
* **Padding** around the text block
* Horizontal and vertical alignment: `left` / `center` / `right`, `top` / `middle` / `bottom`
* **Per‑axis offset** (`x_shift`, `y_shift`)
* Automatic multiline wrapping based on image width
* **Line spacing**, **letter spacing** and **padding** parameters for advanced multi-line composition.
* **Independent X/Y shifting** for fine-tuned manual positioning.
* **Full hex color control** for both text fill and stroke.
* Supports transparent overlays for non-destructive compositing.
### ✔️ 3. Styling Options
### 🧭 3. Smarter Alignment and Positioning
* Fill + alpha
* Stroke + alpha
* Shadow + alpha + offset
* Rounded background box with padding, color, radius, and alpha
* Inline rich text styling with HTML-like tags
* Expanded support for:
### ✔️ 4. Inline Rich Text
* Horizontal alignment: `left`, `center`, `right`
* Vertical alignment: `top`, `middle`, `bottom`
* Reliable anchor-based alignment even on varying resolutions and batch images.
The `text` field now supports a small HTML-like subset for styling parts of the text independently.
### 🔠 4. Font Management
Supported tags:
* Custom fonts can be loaded directly from the repo’s `/fonts` directory.
* Supports both `.ttf` and `.otf` formats.
* Graceful fallback to a system font if a specified font is unavailable.
* `<b>bold</b>`
* `<i>italic</i>`
* `<br>` for line breaks
* `<span color="#FF0000">red text</span>`
* `<span bg="#000000">highlighted text</span>`
* `<span align="center">centered line block</span>`
* `<div align="right">right-aligned line block</div>`
* `<span style="color:#00FF00; background-color:#222222">combined styling</span>`
### ⚙️ 5. Batch and Workflow Enhancements
Supported attributes on `<span>`:
* Native **batch support** — automatically applies consistent overlay logic across all input images.
* Optimized performance and memory use when processing large image sets.
* Node parameters persist and preview correctly in ComfyUI sessions.
| Attribute | Purpose |
| --------- | ------- |
| `color` | Per-segment text color |
| `fill` | Alias for text color |
| `fg` | Alias for text color |
| `bg` | Per-segment highlight/background color |
| `background` | Alias for background color |
| `background-color` | Alias for background color |
| `align` | Overrides line alignment for that styled portion (`left`, `center`, `right`) |
| `style` | Inline CSS-like support for `color` and `background-color` |
### 🧩 6. Developer & Extensibility Features
Examples:
* Modularized code for easier maintenance and feature additions.
* Clean separation between text layout and rendering logic.
* Ready for further extensions such as shadow layers, gradient text, or dynamic variable injection.
```html
Hello <b>bold</b> and <i>italic</i><br>
<span color="#ff0000">red</span> and <span color="#00aaff" bg="#111111">blue on dark background</span>
```
Notes:
* This is **not full HTML/CSS rendering** — it is a safe, limited inline styling system.
* Plain text still works exactly as before.
* Bold and italic use best-effort font variant matching. If the selected font has no matching variant, the regular font is used.
* Inline background highlights use the node's `bg_alpha` / `bg_radius` settings for opacity and rounding.
* Inline alignment overrides the node's `font_alignment` for the wrapped portion only.
* Invalid inline colors fall back to the node's normal `fill_color_hex` or `bg_color_hex` values.
* Unsupported HTML tags are ignored for styling; their text content still renders.
### ✔️ 5. Rich Text Examples
#### Mixed emphasis
```html
This is <b>bold</b>, <i>italic</i>, and <b><i>both</i></b>.
```
#### Multi-color text
```html
<span color="#ff5555">Red</span>
<span color="#55ff55">Green</span>
<span color="#5599ff">Blue</span>
```
#### Highlighted words only
```html
Normal text with <span bg="#6b1d1d">highlighted words</span> in the middle.
```
#### Combined color + highlight + emphasis
```html
<span color="#ffffff" bg="#004488"><b>Important label</b></span>
```
#### Using inline style
```html
<span style="color:#ffd700; background-color:#202020">Styled with inline attributes</span>
```
#### Multiline rich text
```html
Title: <b>Episode 01</b><br>
<span color="#cccccc">Subtitle line</span><br>
<span color="#ffffff" bg="#7a0018">Now Playing</span>
```
#### Alignment override inside a left-aligned node
```html
Intro line<br>
<span align="center"><b>Centered title</b></span><br>
Ending line
```
#### Right-aligned callout block
```html
<div align="right"><span bg="#222222" color="#ffffff">Right callout</span></div>
```
### ✔️ 6. Animation System
Supports:
* `fade_in`
* `fade_out`
* `move_from_top`
* `move_from_bottom`
* `move_from_left`
* `move_from_right`
With easing:
* `linear`
* `ease_in`
* `ease_out`
* `ease_in_out`
Animation Parameters:
* `animation_frames`
* `animation_opacity_target`
* `pause_frames_before_start`
### ✔️ 7. Batch-Smart Processing
* Automatically caches layout on first frame
* Maintains perfect consistency across all frames
* For animated batches: animates through first *N* frames, then holds the final pose
### ✔️ 8. Full Video Support
The **Advanced Text Overlay – Video** node:
* Reads a video frame‑by‑frame
* Applies the same overlay logic
* Writes a new MP4
* **Automatically re‑injects the original audio track with ffmpeg**
* Optional `delete_original` flag
### ✔️ 9. Instagram Question Box
Two dedicated nodes — **`Instagram Question Box`** (image) and **`Instagram Question Box – Video`** — render a two-panel sticker that replicates the Instagram "Ask me a question" story format.
**Layout**
```
┌──────────────────────────────────┐
│ Ask me a question ← header │ ← header_height px, header_color_hex
├──────────────────────────────────┤
│ │
│ Your answer text here │ ← body_height px, body_color_hex
│ │
└──────────────────────────────────┘
```
* All four outer corners are fully rounded (`corner_radius`) with **anti-aliased edges** (4× supersampled rendering)
* The header/body divider is flat — only the outer corners are rounded
* Optional soft drop-shadow (`box_shadow_enable`, `box_shadow_blur`, `box_shadow_distance`)
**Typography**
* Separate font, size, letter spacing, and fill color/alpha for **question** (header) and **answer** (body)
* `answer_line_spacing` controls vertical gap between wrapped lines
* `answer_padding` sets inner padding on all sides of the body text area
* Answer text supports the same **HTML-like rich text** as the main overlay nodes (`<b>`, `<i>`, `<br>`, `<span color="…">`, `\n`) — bold/italic load the correct font variant automatically
**Positioning & Animation**
* `box_width`, `header_height`, `body_height` — independent pixel dimensions
* `horizontal_alignment` / `vertical_alignment` + `x_shift` / `y_shift` for placement
* Same full animation system as the main nodes (`fade_in`, `move_from_*`, easing, `pause_frames_before_start`)
**Video variant extras**
* `pause_seconds_before_start` (converted to frames using source FPS)
* Audio preserved via ffmpeg re-mux
* Optional `delete_original`
**Default values** are tuned for a 1080-wide portrait story:
| Parameter | Default |
| --------- | ------- |
| `box_width` | 950 |
| `header_height` | 90 |
| `body_height` | 200 |
| `corner_radius` | 32 |
| `question_font` | Arial Black |
| `question_font_size` | 30 |
| `answer_font_size` | 50 |
| `box_shadow_blur` | 15 |
---
## ⚙️ Installation
## 📥 Installation
To install the **Advanced Text Overlay** node:
Clone the repository inside your ComfyUI `custom_nodes` directory:
1. Locate your ComfyUI `custom_nodes` directory.
Typically found at:
```bash
git clone https://github.com/scofano/ComfyUI-Advanced-TextOverlay
```
```
ComfyUI/custom_nodes/
```
2. Clone this repository into that directory:
Install the required dependencies:
```bash
pip install -r requirements.txt
```
Restart ComfyUI — the nodes will appear under **Advanced Text Overlay**.
## 🎨 Font Selection & Name Resolution
The node includes a robust font management system that automatically populates with fonts installed on your system:
- **Windows**: Scans `C:\Windows\Fonts\`
- **macOS**: Scans `/System/Library/Fonts/`, `/Library/Fonts/`, and `~/Library/Fonts/`
- **Linux**: Scans `/usr/share/fonts/`, `/usr/local/share/fonts/`, `~/.fonts/`, and `~/.local/share/fonts/`
### True Font Name Resolution
Unlike many other nodes that use the font's filename (e.g., `califb.ttf` → "califb"), this module uses the **True Font Family Name** (e.g., "Californian FB").
- **Why this matters**: Rendering engines often fail to find fonts by filename, causing them to fall back to **Arial**. By resolving the internal metadata name, this module ensures your selected font is rendered correctly every time.
- **Performance Caching**: Scanning hundreds of system fonts can be slow. This module automatically creates a `font_cache.json` file. The first scan takes a few seconds, but subsequent loads are near-instant (<1ms).
### Using the Font Dropdown
1. Select your desired font from the dropdown menu.
2. The node will automatically resolve the "True Name" to the correct file path.
3. Text will be rendered using the selected font, avoiding the "Arial fallback" bug.
4. Works with both the image and video overlay nodes.
### For Developers
The font system is modular and can be reused in other ComfyUI nodes:
```python
from .font_utils import get_available_fonts, get_font_path
class MyTextNode:
@classmethod
def INPUT_TYPES(cls):
fonts = get_available_fonts()
return {
"required": {
"font": (fonts, {"default": fonts[0] if fonts else "Arial"}),
# ... other inputs
}
}
```
This provides a consistent font selection experience across all your nodes.
```bash
git clone https://github.com/scofano/ComfyUI-Advanced-TextOverlay
```
3. Restart ComfyUI.
The node will appear under the **`image/text`** category as **“Advanced Text Overlay”**.
---
## 🚀 Usage
1. Add the **Advanced Text Overlay** node to your ComfyUI workflow.
2. Connect your image source to the node’s **image input**.
3. Configure text and style parameters:
### **Image Node: `Advanced Text Overlay`**
* Text content
* Font and size
* Fill and stroke colors
* Alignment, spacing, and offset adjustments
4. Connect the node’s output to your desired destination (e.g., preview, save image).
Connect an image or batch → configure text parameters → render.
You can enter either plain text:
```text
Hello world
Second line
```
or rich text:
```html
Hello <b>world</b><br><span color="#00d0ff">Second line</span>
```
You can also override line alignment for just part of the content:
```html
<span align="center">Centered line</span>
```
### **Video Node: `Advanced Text Overlay – Video`**
Provide a path to a video → configure overlay → output is written to ComfyUI's output directory.
---
## 🔧 Input Parameters
| Parameter | Type | Description |
| ------------------------ | ------------- | ----------------------------------------------------------------------------- |
| **image** | Image / Batch | Input image(s) to overlay text onto. |
| **text** | String | The text content to render on the image. Supports multi-line text. |
| **font** | String | Font name or filename (e.g., `arial.ttf`). Looks in `/fonts` or system fonts. |
| **font_size** | Integer | Size of the text in pixels. |
| **fill_color_hex** | String | Text fill color in hex format (e.g., `#FFFFFF`). |
| **stroke_color_hex** | String | Stroke (outline) color in hex format (e.g., `#000000`). |
| **stroke_thickness** | Float | Thickness of the text outline. |
| **padding** | Float | Additional space around the text block. Useful for multi-line layouts. |
| **horizontal_alignment** | Enum | `left`, `center`, or `right` horizontal alignment. |
| **vertical_alignment** | Enum | `top`, `middle`, or `bottom` vertical alignment. |
| **x_shift** | Float | Horizontal offset adjustment (pixels). |
| **y_shift** | Float | Vertical offset adjustment (pixels). |
| **line_spacing** | Float | Vertical spacing between lines of text. |
| **letter_spacing** | Float | Horizontal spacing between letters. |
### Text & Typography
| Parameter | Description |
| ---------------- | ----------------------------------------- |
| `text` | Text to draw; supports multiline `\n` and inline HTML-like tags such as `<b>`, `<i>`, `<br>`, and `<span color/bg>` |
| `all_caps` | Force uppercase |
| `font` | Font name or file; auto-searches `/fonts` |
| `font_size` | Pixel size |
| `letter_spacing` | Per-character spacing |
| `line_spacing` | Spacing between lines |
### Rich Text Reference
| Syntax | Result |
| ------ | ------ |
| `<b>Text</b>` | Bold text when a matching font variant is available |
| `<i>Text</i>` | Italic text when a matching font variant is available |
| `<br>` | Line break |
| `<span color="#RRGGBB">Text</span>` | Changes text color for that segment |
| `<span bg="#RRGGBB">Text</span>` | Draws a background highlight behind that segment |
| `<span align="center">Text</span>` | Centers the wrapped portion regardless of the node's `font_alignment` |
| `<div align="right">Text</div>` | Right-aligns the wrapped portion regardless of the node's `font_alignment` |
| `<span style="color:#fff; background-color:#000">Text</span>` | Combined inline styling |
### Rich Text Examples
```html
Normal <b>bold</b> <i>italic</i>
```
```html
<span color="#FFD700">Gold</span> <span color="#00FFFF">Cyan</span>
```
```html
<span bg="#8B0000">Red highlight</span> and <span color="#FFFFFF" bg="#004488">white on blue</span>
```
```html
<span color="#FFD700"><b>Golden title</b></span><br>
<span style="color:#ffffff; background-color:#333333">subtitle</span>
```
```html
Top line<br>
<span align="center" color="#ffffff" bg="#444444"><b>Centered mid title</b></span><br>
Bottom line
```
### Rich Text Limitations
* This is **not** a full browser engine and does not support general HTML layout or arbitrary CSS.
* Supported styling is intentionally limited to inline emphasis, line breaks, text color, and segment background color.
* Per-segment backgrounds wrap naturally with the text layout; if text wraps, the highlight is drawn for each wrapped fragment.
* Alignment overrides operate at the wrapped line/block level. If alignment changes mid-text, the renderer starts a new aligned line block for that portion.
* Whole-block background settings (`bg_enable`, `bg_padding`, `bg_color_hex`, etc.) still apply independently from inline segment highlights.
### Color & Stroke
| Parameter | Description |
| ------------------ | ---------------------------------------- |
| `fill_color_hex` | Hex fill color |
| `fill_alpha` | Opacity 0–1 |
| `stroke_enable` | Toggle outline |
| `stroke_color_hex` | Hex outline color |
| `stroke_alpha` | Outline opacity |
| `stroke_thickness` | Relative thickness (scaled by font size) |
### Layout & Position
| Parameter | Description |
| ---------------------- | --------------------------- |
| `padding` | Padding around text block |
| `horizontal_alignment` | `left` / `center` / `right` |
| `vertical_alignment` | `top` / `middle` / `bottom` |
| `x_shift`, `y_shift` | Pixel offsets |
### Background Box
| Parameter | Description |
| -------------- | ------------------------- |
| `bg_enable` | Toggle background box |
| `bg_padding` | Padding around text block |
| `bg_radius` | Rounded corner radius |
| `bg_color_hex` | Hex color |
| `bg_alpha` | Opacity |
### Shadow
| Parameter | Description |
| ------------------ | ------------- |
| `shadow_enable` | Toggle shadow |
| `shadow_distance` | Pixel offset |
| `shadow_color_hex` | Shadow color |
| `shadow_alpha` | Opacity |
### Animation
| Parameter | Description |
| --------------------------- | ------------------------------- |
| `animate` | Enable animation |
| `animation_kind` | Fade or movement animation |
| `animation_frames` | How many frames animation lasts |
| `animation_opacity_target` | Target alpha |
| `animation_ease` | Easing curve |
| `pause_frames_before_start` | Delay before animation begins |
### Video-Specific Parameters
| Parameter | Description |
| ---------------------------- | ------------------------------------ |
| `video_path` | Input video |
| `filename_prefix` | Naming prefix for output |
| `delete_original` | Remove input after processing |
| `pause_seconds_before_start` | Pause duration (converted to frames) |
---
## 🖼️ Output
## 🧪 Output
The node outputs the modified image (or batch of images) with the specified text overlay applied according to the given configuration.
* Image node → modified image/batch
* Video node → MP4 written to ComfyUI output directory, with audio preserved
---
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 629 KiB

+48
View File
@@ -0,0 +1,48 @@
from typing import Tuple
# Easing -------------------------------------------------------------
def progress(t: int, T_minus1: int, ease: str) -> float:
if T_minus1 <= 0:
return 1.0
x = float(t) / float(T_minus1)
if ease == "linear":
return x
if ease == "ease_in":
return x * x
if ease == "ease_out":
return 1 - (1 - x) * (1 - x)
if ease == "ease_in_out":
if x < 0.5:
return 2 * x * x
return 1 - (-2 * x + 2) ** 2 / 2
return x
# Opacity ------------------------------------------------------------
def compute_opacity(kind: str, p: float, target: float) -> float:
"""
kind: 'fade_in' | 'fade_out' | others
p in [0,1], target in [0,1]
"""
target = max(0.0, min(1.0, float(target)))
if kind == "fade_out":
return target * (1.0 - p)
if kind == "fade_in":
return target * p
return target
# Offsets ------------------------------------------------------------
def compute_offsets(kind: str, p: float, img_w: int, img_h: int) -> Tuple[int, int]:
"""
Move from off-screen-ish towards the final position.
p=0 => max offset, p=1 => 0 offset.
"""
dx = dy = 0.0
if kind == "move_from_left":
dx = -(img_w * (1.0 - p) * 0.5)
elif kind == "move_from_right":
dx = (img_w * (1.0 - p) * 0.5)
elif kind == "move_from_top":
dy = -(img_h * (1.0 - p) * 0.5)
elif kind == "move_from_bottom":
dy = (img_h * (1.0 - p) * 0.5)
return int(round(dx)), int(round(dy))
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

+291
View File
@@ -0,0 +1,291 @@
"""
Font utilities for ComfyUI nodes.
Provides cross-platform font discovery and management functionality.
"""
import os
import sys
import glob
import re
import json
from typing import List, Dict, Optional
from pathlib import Path
try:
from fontTools import ttLib
except ImportError:
ttLib = None
CACHE_FILE = Path(__file__).parent / "font_cache.json"
class FontManager:
"""Centralized font discovery and management for ComfyUI nodes."""
def __init__(self):
self._name_to_path: Dict[str, str] = {}
self._scanned = False
self._load_cache()
def _load_cache(self) -> None:
"""Load font name mapping from JSON cache if it exists."""
if CACHE_FILE.exists():
try:
with open(CACHE_FILE, "r", encoding="utf-8") as f:
self._name_to_path = json.load(f)
if self._name_to_path:
self._scanned = True
except Exception as e:
print(f"Error loading font cache: {e}")
def _save_cache(self) -> None:
"""Save current font mapping to JSON cache."""
try:
with open(CACHE_FILE, "w", encoding="utf-8") as f:
json.dump(self._name_to_path, f, indent=4)
except Exception as e:
print(f"Error saving font cache: {e}")
def _get_font_family_name(self, font_path: str) -> Optional[str]:
"""Extract the Font Family name using fontTools."""
if ttLib is None:
return None
try:
# Use lazy=True to avoid loading the entire font into memory
# For .ttc files, we just look at the first font (fontNumber=0)
font = ttLib.TTFont(font_path, fontNumber=0, lazy=True)
name_record = None
if 'name' in font:
for record in font['name'].names:
# Name ID 1 = Font Family
if record.nameID == 1:
# Priority: Windows Platform (3) + US English (1033)
if record.platformID == 3 and record.langID == 1033:
name_record = record
break
# Fallback to any English or the first record found
elif record.langID == 1033 or name_record is None:
name_record = record
if name_record:
return name_record.toUnicode()
except Exception:
pass
return None
def get_available_fonts(self) -> List[str]:
"""
Returns a sorted list of available font names for dropdown menus.
Caches results for performance.
"""
if not self._scanned:
self._scan_system_fonts()
return sorted(self._name_to_path.keys())
def get_font_path(self, font_name: str) -> str:
"""
Converts a font name to its actual file path.
Returns the original name if not found in our cache (backward compatibility).
"""
if not self._scanned:
self._scan_system_fonts()
return self._name_to_path.get(font_name, font_name)
def get_font_variant_path(self, font_name: str, bold: bool = False, italic: bool = False) -> str:
"""
Best-effort lookup for bold/italic variants of a selected font.
If a matching variant cannot be found, returns the regular font path.
"""
if not self._scanned:
self._scan_system_fonts()
base_path = self.get_font_path(font_name)
if not bold and not italic:
return base_path
if bold and italic:
return (
self._find_variant_path(font_name, base_path, bold=True, italic=True)
or self._find_variant_path(font_name, base_path, bold=True, italic=False)
or self._find_variant_path(font_name, base_path, bold=False, italic=True)
or base_path
)
return self._find_variant_path(font_name, base_path, bold=bold, italic=italic) or base_path
def _normalize_font_name(self, value: str) -> str:
return re.sub(r"[^a-z0-9]+", "", (value or "").lower())
def _find_variant_path(self, font_name: str, base_path: str, bold: bool = False, italic: bool = False) -> Optional[str]:
normalized_targets = []
if font_name:
normalized_targets.append(self._normalize_font_name(font_name))
if base_path and os.path.exists(base_path):
base_stem = Path(base_path).stem
normalized_targets.append(self._normalize_font_name(base_stem))
normalized_targets = [t for t in dict.fromkeys(normalized_targets) if t]
if not normalized_targets:
return None
bold_markers = ["bold", "semibold", "demibold", "extrabold", "black", "heavy"]
italic_markers = ["italic", "oblique", "slanted", "kursiv"]
best_match = None
best_score = None
for candidate_name, candidate_path in self._name_to_path.items():
normalized_name = self._normalize_font_name(candidate_name)
if not normalized_name:
continue
if not any(target in normalized_name or normalized_name in target for target in normalized_targets):
continue
if bold and not any(marker in normalized_name for marker in bold_markers):
continue
if italic and not any(marker in normalized_name for marker in italic_markers):
continue
score = len(normalized_name)
if bold and "bold" in normalized_name:
score -= 10
if italic and "italic" in normalized_name:
score -= 10
for target in normalized_targets:
if normalized_name == target:
score -= 2
elif normalized_name.startswith(target) or target.startswith(normalized_name):
score -= 1
if best_score is None or score < best_score:
best_score = score
best_match = candidate_path
return best_match
def _scan_system_fonts(self) -> None:
"""Platform-specific font discovery."""
if self._scanned and self._name_to_path:
return
self._name_to_path = {}
if sys.platform == "win32":
self._scan_windows_fonts()
elif sys.platform == "darwin": # macOS
self._scan_macos_fonts()
else: # Linux and other Unix-like systems
self._scan_linux_fonts()
# Add some common fallback fonts
self._add_fallback_fonts()
# Save results to cache for next time
self._save_cache()
self._scanned = True
def _scan_windows_fonts(self) -> None:
"""Scan Windows system fonts directory."""
try:
fonts_dir = Path(os.environ.get("WINDIR", "C:\\Windows")) / "Fonts"
if fonts_dir.exists():
self._scan_directory(fonts_dir)
except Exception:
pass
def _scan_macos_fonts(self) -> None:
"""Scan macOS system fonts directories."""
font_dirs = [
Path("/System/Library/Fonts"),
Path("/Library/Fonts"),
Path.home() / "Library/Fonts"
]
for fonts_dir in font_dirs:
if fonts_dir.exists():
self._scan_directory(fonts_dir)
def _scan_linux_fonts(self) -> None:
"""Scan Linux system fonts directories."""
font_dirs = [
Path("/usr/share/fonts"),
Path("/usr/local/share/fonts"),
Path.home() / ".fonts",
Path.home() / ".local/share/fonts"
]
for fonts_dir in font_dirs:
if fonts_dir.exists():
self._scan_directory(fonts_dir)
def _scan_directory(self, fonts_dir: Path) -> None:
"""Scan a directory for font files."""
patterns = ["*.ttf", "*.otf", "*.ttc", "*.woff", "*.woff2"]
for pattern in patterns:
for font_file in fonts_dir.glob(f"**/{pattern}"):
try:
# Try to get the internal font family name
font_path = str(font_file)
font_name = self._get_font_family_name(font_path)
# Fallback to filename stem if metadata extraction fails
if not font_name:
font_name = font_file.stem
# Only add if we don't already have this name (avoid duplicates)
if font_name not in self._name_to_path:
self._name_to_path[font_name] = font_path
except Exception:
continue
def _add_fallback_fonts(self) -> None:
"""Add common fallback fonts that might not be detected."""
fallbacks = {
"Arial": "Arial",
"Times New Roman": "Times New Roman",
"Courier New": "Courier New",
"Helvetica": "Helvetica",
"Verdana": "Verdana",
"Georgia": "Georgia",
"Trebuchet MS": "Trebuchet MS",
"Impact": "Impact",
"Comic Sans MS": "Comic Sans MS",
"Arial Black": "Arial Black",
"Tahoma": "Tahoma",
"Lucida Sans Unicode": "Lucida Sans Unicode",
"MS Sans Serif": "MS Sans Serif",
"MS Serif": "MS Serif",
"Palatino Linotype": "Palatino Linotype",
"Arial Narrow": "Arial Narrow"
}
for name, path in fallbacks.items():
if name not in self._name_to_path:
self._name_to_path[name] = path
# Global instance for easy reuse across nodes
font_manager = FontManager()
def get_available_fonts() -> List[str]:
"""Convenience function to get available fonts."""
return font_manager.get_available_fonts()
def get_font_path(font_name: str) -> str:
"""Convenience function to get font path from name."""
return font_manager.get_font_path(font_name)
def get_font_variant_path(font_name: str, bold: bool = False, italic: bool = False) -> str:
"""Convenience function to get a bold/italic font variant path if available."""
return font_manager.get_font_variant_path(font_name, bold=bold, italic=italic)
+1756 -183
View File
File diff suppressed because it is too large Load Diff
+20 -6
View File
@@ -1,13 +1,27 @@
# pyproject.toml
[project]
name = "advanced-textoverlay" # Unique and immutable node identifier.
description = "Advanced Text Overlay Node for ComfyUI featuring rebuilt rendering logic, precise alignment, custom fonts, and extended styling controls."
version = "1.1.0" # Semantic versioning
dependencies = [] # Populated from requirements.txt if present.
name = "advanced-textoverlay"
description = "4 nodes: Advanced Text Overlay (image + video) with rich HTML text, animations, and full typography controls; Instagram Question Box (image + video) with two-panel sticker layout, anti-aliased corners, independent header/body heights, and soft drop-shadow."
version = "1.5.0"
readme = "README.md"
requires-python = ">=3.9"
license = { file = "LICENSE" }
dependencies = [
"numpy",
"torch",
"Pillow",
"imageio",
"tqdm",
]
classifiers = [
"Operating System :: OS Independent",
]
[project.urls]
Repository = "https://github.com/scofano/ComfyUI-Advanced-TextOverlay"
Documentation = "https://github.com/scofano/ComfyUI-Advanced-TextOverlay#readme"
Issues = "https://github.com/scofano/ComfyUI-Advanced-TextOverlay/issues"
[tool.comfy]
PublisherId = "dansco"
DisplayName = "Advanced Text Overlay"
DisplayName = "Advanced Text Overlay"
Icon = "https://raw.githubusercontent.com/scofano/ComfyUI-Advanced-TextOverlay/refs/heads/main/ato-icon.jpg"
+4 -1
View File
@@ -1,3 +1,6 @@
numpy
torch
Pillow
Pillow
imageio
tqdm
fonttools