Files
Mohammad EssamandClaude Opus 5.5 a538d3a75e feat(python): draw Arabic text in vis() with khatt from the rtl extra (#763)
* feat(python): draw Arabic text in vis() with khatt from the rtl extra

Recognized Arabic, Persian and Urdu text is returned in reading order,
but vis() draws it with ImageDraw.text, which only joins Arabic letters
and lays out right-to-left text when Pillow is built with Raqm. Without
it the visualization shows separate letters in reverse order, running
out of the box.

When khatt is installed (now part of the rtl extra on Python 3.10+),
right-to-left lines are shaped, start at the right edge of their box,
are centered vertically and shrink to fit the box. Other text, and RTL
text without khatt, is drawn by Pillow as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* style(python): ruff format the vis RTL changes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(python): let RTL text shrink below 10px to fit small boxes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 22:01:00 +08:00

138 lines
3.8 KiB
Python

# -*- encoding: utf-8 -*-
# @Author: SWHL
# @Contact: liekkaskono@163.com
import sys
from pathlib import Path
import pytest
from PIL import Image, ImageDraw, ImageOps
root_dir = Path(__file__).resolve().parent.parent
sys.path.append(str(root_dir))
from rapidocr import LangRec, ModelType, OCRVersion, RapidOCR
from rapidocr.utils import vis_res
tests_dir = root_dir / "tests" / "test_files"
img_path = tests_dir / "ch_en_num.jpg"
@pytest.fixture()
def engine():
engine = RapidOCR()
return engine
def test_vis_only_det(engine):
img_path = tests_dir / "ch_en_num.jpg"
result = engine(img_path, use_det=True, use_cls=False, use_rec=False)
vis_img = result.vis()
assert vis_img.shape[2] == 3
def test_vis_only_rec(engine):
img_path = tests_dir / "text_rec.jpg"
result = engine(img_path, use_det=False, use_cls=False, use_rec=True)
vis_img = result.vis()
assert vis_img.shape[2] == 3
def test_vis_only_cls(engine):
img_path = tests_dir / "text_rec.jpg"
result = engine(img_path, use_det=False, use_cls=True, use_rec=False)
vis_img = result.vis()
assert vis_img.shape[2] == 3
def test_vis_det_cls_rec(engine):
img_path = tests_dir / "ch_en_num.jpg"
result = engine(img_path, use_det=True, use_cls=True, use_rec=True)
vis_img = result.vis()
assert vis_img.shape[2] == 3
ARABIC_TEXT = "امروز هوا خوبه"
def ink_box(img):
return ImageOps.invert(img.convert("L")).getbbox()
def test_vis_draws_rtl_text_with_khatt(monkeypatch):
khatt = pytest.importorskip("khatt")
drawn = []
draw_text = khatt.draw_text
def spy(img, xy, txt, **kwargs):
drawn.append(txt)
return draw_text(img, xy, txt, **kwargs)
monkeypatch.setattr(vis_res.khatt, "draw_text", spy)
engine = RapidOCR(
params={
"Rec.lang_type": LangRec.ARABIC,
"Rec.model_type": ModelType.MOBILE,
"Rec.ocr_version": OCRVersion.PPOCRV5,
}
)
result = engine(
tests_dir / "arabic.png", use_det=False, use_cls=False, use_rec=True
)
vis_img = result.vis()
assert vis_img.shape[2] == 3
assert drawn == list(result.txts)
def test_rtl_line_starts_at_the_right_edge_and_fits_its_box():
pytest.importorskip("khatt")
vis = vis_res.VisRes(lang_type=LangRec.ARABIC)
img = Image.new("RGB", (300, 60), (255, 255, 255))
box = [[20, 10], [280, 10], [280, 50], [20, 50]]
vis.draw_line(img, ImageDraw.Draw(img), box, ARABIC_TEXT, 40)
left, top, right, bottom = ink_box(img)
assert right == 280
assert left >= 20 and top >= 10 and bottom <= 50
def test_rtl_line_shrinks_below_10px_to_fit_a_small_box():
pytest.importorskip("khatt")
vis = vis_res.VisRes(lang_type=LangRec.ARABIC)
img = Image.new("RGB", (100, 30), (255, 255, 255))
box = [[20, 10], [60, 10], [60, 18], [20, 18]]
vis.draw_line(img, ImageDraw.Draw(img), box, ARABIC_TEXT, 10)
left, top, right, bottom = ink_box(img)
assert left >= 20 and right <= 60 and top >= 10 and bottom <= 18
def test_ltr_line_is_drawn_by_pillow(monkeypatch):
pytest.importorskip("khatt")
monkeypatch.setattr(
vis_res.khatt, "draw_text", lambda *args, **kwargs: pytest.fail("khatt used")
)
vis = vis_res.VisRes()
img = Image.new("RGB", (300, 60), (255, 255, 255))
vis.draw_line(
img,
ImageDraw.Draw(img),
[[20, 10], [280, 10], [280, 50], [20, 50]],
"Hello",
32,
)
assert ink_box(img) is not None
def test_rtl_line_without_khatt_is_drawn_by_pillow(monkeypatch):
monkeypatch.setattr(vis_res, "khatt", None)
vis = vis_res.VisRes(lang_type=LangRec.ARABIC)
img = Image.new("RGB", (300, 60), (255, 255, 255))
vis.draw_line(
img,
ImageDraw.Draw(img),
[[20, 10], [280, 10], [280, 50], [20, 50]],
ARABIC_TEXT,
32,
)
assert ink_box(img)[0] >= 20