File size: 3,951 Bytes
dbe2c62
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
import re
import fitz

from typing import Dict, Tuple, Union

class PDFQualityChecker:
    """
    Bộ lọc chất lượng PDF cơ bản trước khi xử lý.
    Đánh giá lỗi font, lỗi encode, ký tự hỏng, OCR kém, v.v.
    """

    def __init__(self, 
                 max_invalid_ratio: float = 0.2,
                 max_whitespace_ratio: float = 0.2,
                 max_short_line_ratio: float = 0.3,
                 min_total_chars: int = 300):
        self.max_invalid_ratio = max_invalid_ratio
        self.max_whitespace_ratio = max_whitespace_ratio
        self.max_short_line_ratio = max_short_line_ratio
        self.min_total_chars = min_total_chars

        # Regex nhận diện ký tự hợp lệ (chữ, số, dấu tiếng Việt, ký hiệu cơ bản)
        self.valid_char_pattern = re.compile(r"[A-Za-zÀ-ỹĐđ0-9.,:;!?()\"'’”“–\-_\s]")

    # ============================================================
    # 1️⃣  HÀM CHÍNH
    # ============================================================
    def evaluate(self, pdf: Union[str, fitz.Document]) -> Tuple[bool, Dict]:
        """
        Đánh giá chất lượng PDF.
        - pdf: đường dẫn (str) hoặc fitz.Document đã mở
        - trả (is_good, metrics)
        """
        # ---- Chuẩn hóa input ----
        if isinstance(pdf, str):
            try:
                doc = fitz.open(pdf)
            except Exception as e:
                return False, {"check_mess": f"❌ Không mở được file: {e}"}
        elif isinstance(pdf, fitz.Document):
            doc = pdf
        else:
            raise TypeError("pdf phải là str hoặc fitz.Document")

        # ---- Bắt đầu thống kê ----
        text_all = ""
        short_lines = 0
        all_lines = 0

        for page in doc:
            text = page.get_text("text") or ""
            if not text.strip():
                continue
            lines = text.splitlines()
            for line in lines:
                if not line.strip():
                    continue
                all_lines += 1
                if len(line.strip()) < 10:
                    short_lines += 1
            text_all += text + "\n"

        total_chars = len(text_all)
        if total_chars < self.min_total_chars:
            return False, {
                "check_mess": "❌ File quá ngắn hoặc không có text layer",
                "total_chars": total_chars,
            }

        # ---- Tính tỷ lệ lỗi ----
        valid_chars = sum(1 for ch in text_all if self.valid_char_pattern.match(ch))
        invalid_chars = total_chars - valid_chars
        invalid_ratio = invalid_chars / total_chars

        whitespace_excess = len(re.findall(r" {3,}", text_all))
        whitespace_ratio = whitespace_excess / total_chars

        short_line_ratio = short_lines / max(all_lines, 1)

        # ---- Đưa ra kết luận ----
        is_good = (
            invalid_ratio <= self.max_invalid_ratio
            and whitespace_ratio <= self.max_whitespace_ratio
            and short_line_ratio < 1
        )

        if not is_good:
            if invalid_ratio > self.max_invalid_ratio:
                check_mess = "❌ Nhiều ký tự lỗi / encode sai"
            elif whitespace_ratio > self.max_whitespace_ratio:
                check_mess = "❌ Nhiều khoảng trắng thừa"
            elif short_line_ratio >= 1:
                check_mess = "⚠️ OCR hoặc mất ký tự"
            else:
                check_mess = "❌ Văn bản lỗi nặng"
        else:
            check_mess = "✅ Đạt yêu cầu"

        metrics = {
            "check_mess": check_mess,
            "total_chars": total_chars,
            "invalid_ratio": round(invalid_ratio, 3),
            "whitespace_ratio": round(whitespace_ratio, 3),
            "short_line_ratio": round(short_line_ratio, 3),
        }
        return is_good, metrics