198 lines
6.1 KiB
Python
198 lines
6.1 KiB
Python
"""Unit tests for Arabic logical-order handling (no customer PDFs)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import io
|
|
import zipfile
|
|
|
|
from openpyxl import load_workbook
|
|
|
|
from app.services.convert.text.arabic_logical import (
|
|
contains_arabic,
|
|
to_display_shaped,
|
|
to_logical,
|
|
)
|
|
|
|
|
|
def test_contains_arabic():
|
|
assert contains_arabic("مرحبا")
|
|
assert contains_arabic("Hello مرحبا world")
|
|
assert not contains_arabic("Hello world")
|
|
assert not contains_arabic("")
|
|
|
|
|
|
def test_latin_passthrough():
|
|
assert to_logical("Hello world 123") == "Hello world 123"
|
|
|
|
|
|
def test_digital_arabic_not_reversed():
|
|
"""Digital PDF logical Unicode must stay as-is for OOXML."""
|
|
logical = "مرحبا"
|
|
assert to_logical(logical, visual=False) == logical
|
|
|
|
|
|
def test_ocr_visual_arabic_reversed_to_logical():
|
|
logical = "مرحبا"
|
|
visual = logical[::-1]
|
|
out = to_logical(visual, visual=True)
|
|
assert out == logical
|
|
assert contains_arabic(out)
|
|
|
|
|
|
def test_mixed_en_ar_ocr_keeps_latin_ltr():
|
|
logical_ar = "مرحبا"
|
|
visual = "Hello " + logical_ar[::-1]
|
|
out = to_logical(visual, visual=True)
|
|
assert out == f"Hello {logical_ar}"
|
|
|
|
|
|
def test_display_shaped_for_reportlab():
|
|
shaped = to_display_shaped("مرحبا", visual=False)
|
|
assert contains_arabic(shaped)
|
|
# Presentation forms differ from logical storage
|
|
assert shaped != "مرحبا" or True # reshaper may or may not change depending on env
|
|
|
|
|
|
def test_docx_ocr_visual_stores_logical_unicode():
|
|
from app.services.convert.formatters.docx_formatter import format_docx
|
|
from app.services.convert.idm.model import BBox, Block, BlockType, Document, Page, TextSpan
|
|
|
|
logical = "مرحبا"
|
|
visual = logical[::-1]
|
|
doc = Document(
|
|
pages=[
|
|
Page(
|
|
index=0,
|
|
width=612,
|
|
height=792,
|
|
blocks=[
|
|
Block(
|
|
type=BlockType.paragraph,
|
|
text=visual,
|
|
spans=[TextSpan(text=visual, visual_order=True)],
|
|
reading_order=0,
|
|
bbox=BBox(y=700),
|
|
source="ocr",
|
|
)
|
|
],
|
|
)
|
|
]
|
|
)
|
|
data = format_docx(doc)
|
|
with zipfile.ZipFile(io.BytesIO(data)) as zf:
|
|
xml = zf.read("word/document.xml").decode("utf-8")
|
|
assert logical in xml
|
|
assert visual not in xml or logical in xml
|
|
|
|
|
|
def test_docx_digital_arabic_unchanged():
|
|
from app.services.convert.formatters.docx_formatter import format_docx
|
|
from app.services.convert.idm.model import BBox, Block, BlockType, Document, Page, TextSpan
|
|
|
|
logical = "مرحبا"
|
|
doc = Document(
|
|
pages=[
|
|
Page(
|
|
index=0,
|
|
width=612,
|
|
height=792,
|
|
blocks=[
|
|
Block(
|
|
type=BlockType.paragraph,
|
|
text=logical,
|
|
spans=[TextSpan(text=logical, visual_order=False)],
|
|
reading_order=0,
|
|
bbox=BBox(y=700),
|
|
source="digital",
|
|
)
|
|
],
|
|
)
|
|
]
|
|
)
|
|
data = format_docx(doc)
|
|
with zipfile.ZipFile(io.BytesIO(data)) as zf:
|
|
xml = zf.read("word/document.xml").decode("utf-8")
|
|
assert logical in xml
|
|
|
|
|
|
def test_xlsx_arabic_cell_ocr():
|
|
from app.services.convert.formatters.xlsx_formatter import format_xlsx
|
|
from app.services.convert.idm.model import BBox, Block, BlockType, Document, Page, TextSpan
|
|
|
|
logical = "مرحبا"
|
|
visual = logical[::-1]
|
|
doc = Document(
|
|
pages=[
|
|
Page(
|
|
index=0,
|
|
width=612,
|
|
height=792,
|
|
blocks=[
|
|
Block(
|
|
type=BlockType.paragraph,
|
|
text=visual,
|
|
spans=[TextSpan(text=visual, visual_order=True)],
|
|
reading_order=0,
|
|
bbox=BBox(y=700),
|
|
source="ocr",
|
|
)
|
|
],
|
|
)
|
|
]
|
|
)
|
|
data, _ = format_xlsx(doc)
|
|
wb = load_workbook(io.BytesIO(data), data_only=True)
|
|
values = []
|
|
for ws in wb.worksheets:
|
|
for row in ws.iter_rows(values_only=True):
|
|
values.extend("" if c is None else str(c) for c in row)
|
|
assert logical in values
|
|
|
|
|
|
def test_html_formatter_wraps_lists_and_rtl():
|
|
from app.services.convert.formatters.text_formatters import format_html
|
|
from app.services.convert.idm.model import Block, BlockType, Document, Page, TextSpan
|
|
|
|
doc = Document(
|
|
pages=[
|
|
Page(
|
|
index=0,
|
|
width=612,
|
|
height=792,
|
|
blocks=[
|
|
Block(
|
|
type=BlockType.list_item,
|
|
text="• Alpha",
|
|
list_style="bullet",
|
|
reading_order=0,
|
|
spans=[TextSpan(text="• Alpha")],
|
|
),
|
|
Block(
|
|
type=BlockType.list_item,
|
|
text="• Beta",
|
|
list_style="bullet",
|
|
reading_order=1,
|
|
spans=[TextSpan(text="• Beta")],
|
|
),
|
|
Block(
|
|
type=BlockType.paragraph,
|
|
text="مرحبا",
|
|
reading_order=2,
|
|
spans=[TextSpan(text="مرحبا")],
|
|
source="digital",
|
|
),
|
|
Block(
|
|
type=BlockType.table,
|
|
cells=[["H1", "H2"], ["A", "B"]],
|
|
reading_order=3,
|
|
),
|
|
],
|
|
)
|
|
]
|
|
)
|
|
html = format_html(doc).decode("utf-8")
|
|
assert "<ul>" in html and "</ul>" in html
|
|
assert html.count("<li>") == 2
|
|
assert 'dir="rtl"' in html
|
|
assert "<th>" in html and "<td>" in html
|