""" This module contains a function to add text to an image with a bounding box. """ import unicodedata import textwrap from PIL import Image, ImageDraw, ImageFont import numpy as np import cv2 import arabic_reshaper from bidi.algorithm import get_display def detect_script(text): """ Detect the script of the text """ scripts = set() for char in text: if char.isalpha(): name = unicodedata.name(char, "") if "LATIN" in name: scripts.add("Latin") elif "ARABIC" in name: scripts.add("Arabic") elif "CYRILLIC" in name: scripts.add("Cyrillic") elif "GREEK" in name: scripts.add("Greek") elif "HEBREW" in name: scripts.add("Hebrew") elif "DEVANAGARI" in name: scripts.add("Devanagari") if not scripts: return "Latin" return list(scripts)[0] def get_font_path(script): if script == "Latin": return "./fonts/NotoSans-Regular.ttf" elif script == "Arabic": return "./fonts/NotoNaskhArabic-Regular.ttf" elif script == "Cyrillic": return "./fonts/NotoSansCyrillic-Regular.ttf" elif script == "Greek": return "./fonts/NotoSansGreek-Regular.ttf" else: return "./fonts/NotoSans-Regular.ttf" def add_text(image: np.ndarray, text: str, contour: np.ndarray): script = detect_script(text) font_path = get_font_path(script) if script == "Arabic": reshaped_text = arabic_reshaper.reshape(text) text = get_display(reshaped_text) pil_image = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)) draw = ImageDraw.Draw(pil_image) x, y, w, h = cv2.boundingRect(contour) line_height = 16 font_size = 14 wrapping_ratio = 0.075 min_font_size = 6 # Minimum font size to prevent going to 0 max_iterations = 20 # Prevent infinite loops wrap_width = max(1, int(w * wrapping_ratio)) wrapped_text = textwrap.fill(text, width=wrap_width, break_long_words=True) font = ImageFont.truetype(font_path, size=font_size) lines = wrapped_text.split("\n") total_text_height = (len(lines)) * line_height iterations = 0 while ( total_text_height > h and font_size > min_font_size and iterations < max_iterations ): line_height = max(line_height - 2, min_font_size) font_size = max(font_size - 2, min_font_size) wrapping_ratio = min(wrapping_ratio + 0.025, 0.5) wrap_width = max(1, int(w * wrapping_ratio)) wrapped_text = textwrap.fill(text, width=wrap_width, break_long_words=True) font = ImageFont.truetype(font_path, size=font_size) lines = wrapped_text.split("\n") total_text_height = (len(lines)) * line_height iterations += 1 # If text still doesn't fit after all adjustments, truncate it if total_text_height > h: max_lines = max(1, h // line_height) lines = lines[:max_lines] if len(lines) < len(wrapped_text.split("\n")): # Add ellipsis to last line if text was truncated if lines: lines[-1] = lines[-1][: max(0, len(lines[-1]) - 3)] + "..." # Vertical centering actual_text_height = len(lines) * line_height text_y = y + max(0, (h - actual_text_height) // 2) for line in lines: text_length = draw.textlength(line, font=font) text_x = x + max( 0, (w - text_length) // 2 ) # Ensure x coordinate is not negative draw.text((text_x, text_y), line, font=font, fill=(0, 0, 0)) text_y += line_height image[:, :, :] = cv2.cvtColor(np.array(pil_image), cv2.COLOR_RGB2BGR)