arafix 0.8.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. arafix-0.8.0/.gitignore +26 -0
  2. arafix-0.8.0/CHANGELOG.md +394 -0
  3. arafix-0.8.0/CONTRIBUTING.md +34 -0
  4. arafix-0.8.0/DEPLOY.md +127 -0
  5. arafix-0.8.0/INTEGRATING.md +113 -0
  6. arafix-0.8.0/LICENSE +21 -0
  7. arafix-0.8.0/PKG-INFO +645 -0
  8. arafix-0.8.0/README.md +594 -0
  9. arafix-0.8.0/RELEASING.md +118 -0
  10. arafix-0.8.0/examples/make_broken_pdf.py +179 -0
  11. arafix-0.8.0/examples/make_multicolumn_pdf.py +69 -0
  12. arafix-0.8.0/examples/quickstart.py +73 -0
  13. arafix-0.8.0/pyproject.toml +115 -0
  14. arafix-0.8.0/src/arafix/__init__.py +210 -0
  15. arafix-0.8.0/src/arafix/adapters.py +83 -0
  16. arafix-0.8.0/src/arafix/cli.py +288 -0
  17. arafix-0.8.0/src/arafix/cmap.py +194 -0
  18. arafix-0.8.0/src/arafix/diagnose.py +417 -0
  19. arafix-0.8.0/src/arafix/evaluate.py +358 -0
  20. arafix-0.8.0/src/arafix/extractors/__init__.py +45 -0
  21. arafix-0.8.0/src/arafix/extractors/base.py +58 -0
  22. arafix-0.8.0/src/arafix/extractors/pymupdf_extractor.py +164 -0
  23. arafix-0.8.0/src/arafix/hygiene.py +164 -0
  24. arafix-0.8.0/src/arafix/integrations/__init__.py +49 -0
  25. arafix-0.8.0/src/arafix/integrations/markitdown_plugin.py +136 -0
  26. arafix-0.8.0/src/arafix/lamalef.py +231 -0
  27. arafix-0.8.0/src/arafix/layout.py +583 -0
  28. arafix-0.8.0/src/arafix/normalize.py +184 -0
  29. arafix-0.8.0/src/arafix/order.py +172 -0
  30. arafix-0.8.0/src/arafix/pipeline.py +537 -0
  31. arafix-0.8.0/src/arafix/py.typed +0 -0
  32. arafix-0.8.0/src/arafix/types.py +232 -0
  33. arafix-0.8.0/src/arafix/unicode_tables.py +271 -0
  34. arafix-0.8.0/tests/test_arafix.py +850 -0
  35. arafix-0.8.0/tests/test_integration_pdf.py +206 -0
  36. arafix-0.8.0/tests/test_layout.py +235 -0
  37. arafix-0.8.0/tests/test_p0_hygiene_blocks.py +213 -0
@@ -0,0 +1,26 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *$py.class
4
+ dist/
5
+ build/
6
+ *.egg-info/
7
+ .eggs/
8
+ .pytest_cache/
9
+ .mypy_cache/
10
+ .ruff_cache/
11
+ .venv/
12
+ venv/
13
+ .env
14
+ .coverage
15
+ htmlcov/
16
+ .tox/
17
+ .nox/
18
+ broken.pdf
19
+ out.txt
20
+ multi_demo.pdf
21
+ multi.pdf
22
+ *.pdf
23
+ !tests/**/*.pdf
24
+ !examples/**/*.pdf
25
+ .DS_Store
26
+ Thumbs.db
@@ -0,0 +1,394 @@
1
+ # سجلّ التغييرات
2
+
3
+ ## 0.8.0
4
+
5
+ **الدرجة البنيوية** — أعمدة RTL، ترويسة/تذييل، جداول من هندسة الجليفات.
6
+
7
+ **جاهزية النشر:** `py.typed`، وصف إنجليزي على PyPI، README ثنائي اللغة،
8
+ `DEPLOY.md` / `CONTRIBUTING.md`، مصنّفات أوضح.
9
+
10
+ **إصلاح CI (macOS):**
11
+
12
+ - بعض الخطوط تُخرج `U+066C` (فاصل الآلاف `٬`) بدل الفاصلة العربية
13
+ `U+060C` (`،`). طيٌّ سياقيّ في `hygiene`.
14
+ - أشكال التشكيل المعزولة (`U+FE78`…) كانت تُفكَّك إلى **مسافة + علامة**؛
15
+ صارت العلامة وحدها. (طيّ التشكيل PF **مبكراً في hygiene يُفسد** العكس —
16
+ يبقى مؤجّلاً بعد الاتجاه.)
17
+ - اختبار التكامل يتسامح بسقوط التشكيل على بعض خطوط macOS، ويرفض
18
+ ``نشُرت`` (عنقودٌ خاطئ).
19
+ - ``test_measured_not_asserted``: CER دون 1٪ على الحروف (ignore_diacritics)؛
20
+ CER الكامل دون 5٪ لأن Arial على macOS يُسقط ~5 علامات تشكيل من SAMPLE.
21
+
22
+ ### المشكلة
23
+
24
+ القراءة الهندسية كانت تضمّ كل جليفات السطر الأفقي معاً. في صحيفة
25
+ بعمودين يخرج «يسار+يمين» سطراً واحداً ممسوخاً. والجداول تُسطَّح.
26
+ والترويسة تختلط بالجسد.
27
+
28
+ ### الحل: `layout.py` — تقسيم بالميزاب لا بمراكز الأسطر
29
+
30
+ 1. **ميازب أفقية (gutters)** على الجليفات أولاً → أعمدة حقيقية
31
+ 2. **ترتيب قراءة RTL** (الأيمن فالأيسر) — قابل لـ `ltr`
32
+ 3. **شريط ترويسة/تذييل** (٨٪ أعلى/أسفل) يُعزل قبل الأعمدة
33
+ 4. **جداول** من محاذاة خلايا داخل العمود → Markdown + `page.tables`
34
+ 5. **`repair_per_block`**: كل سطر/خلية تُشخَّص وحدها ثم يُعاد التجميع
35
+
36
+ ```python
37
+ from arafix import extract_pdf, PipelineConfig, LayoutConfig
38
+
39
+ doc = extract_pdf("paper.pdf", PipelineConfig(
40
+ layout="auto", # linear | columns | full
41
+ layout_config=LayoutConfig(reading_order="rtl"),
42
+ ))
43
+ doc.pages[0].n_columns
44
+ doc.all_tables
45
+ ```
46
+
47
+ ```bash
48
+ arafix extract paper.pdf --layout full --reading-order rtl -v --tables
49
+ python examples/make_multicolumn_pdf.py multi.pdf
50
+ ```
51
+
52
+ `layout="auto"` (الافتراضي): صفحة عمود واحد تبقى كما في 0.7 — بلا مفاجآت.
53
+ `PageResult.layout` / `.blocks` / `.tables` للبنية الكاملة.
54
+
55
+ ## 0.7.0
56
+
57
+ طبقةُ الاسترجاع تصير **قابلة للتركيب** — نظافة استخراج، كتلٌ مستقلة،
58
+ معجم وثيقة، وجسر MarkItDown. بلا كسرٍ لوعد النواة صفر-التبعيّات.
59
+
60
+ ### بوابة النظافة (`hygiene`)
61
+
62
+ محرّكات PDF تُخرج `U+00A0` بدل المسافة و`U+00AD` بدل الشرطة — فتفشل
63
+ مقارنةُ السلاسل وCER يظنّ الخطأ عربياً وهو ترميز. صارت:
64
+
65
+ | الأثر | العلاج |
66
+ |---|---|
67
+ | NBSP ومسافات يونيكود | → مسافة عادية |
68
+ | soft hyphen | → `-` |
69
+
70
+ مرحلة `Stage.HYGIENE` تُبلَّغ في التقرير. تُطفأ بـ
71
+ `PipelineConfig(enable_hygiene=False)` إن كنت تقيس المحرّك لا تريد إخفاءه.
72
+ **اختبارات تكامل PDF على ويندوز خضراء.**
73
+
74
+ ### `repair_blocks` / `fix_table`
75
+
76
+ كل كتلة (خلية، سطر، تسمية) تُشخَّص وحدها. خليةٌ معكوسة لا تعكس جارةً
77
+ سليمة. يقبل `str` و`TextBlock` و`(id, text)` و`dict`.
78
+
79
+ ```python
80
+ from arafix import repair_blocks, fix_table, TextBlock
81
+ fix_table([["…", "OK"]])
82
+ ```
83
+
84
+ وأمر CLI: `arafix blocks` (stdin: سطر = كتلة).
85
+
86
+ ### معجم الوثيقة الداخليّ
87
+
88
+ `harvest_document_lexicon` + `PipelineConfig(harvest_document_lexicon=True)`
89
+ (افتراضيّ في `extract_pdf` و`repair_blocks`): كلمات الصفحة/الكتلة
90
+ الصحيحة تحسم «المجالت» في غيرها — بلا ملف معجم خارجيّ.
91
+
92
+ ### MarkItDown
93
+
94
+ - entry point: `markitdown.plugin` → `arafix`
95
+ - `pip install "arafix[markitdown]"` ثم `MarkItDown(enable_plugins=True)`
96
+ - `fix_markitdown(result)` و`fix_any` و`wrap_callable` في
97
+ `arafix.adapters` / `arafix.integrations`
98
+ - [INTEGRATING.md](INTEGRATING.md)
99
+
100
+ الاختبارات: ١٥٥ ← +٣٠ تقريباً.
101
+
102
+ ## 0.6.0
103
+
104
+ بنشمارك خارجيّ جادّ (٣٠ تكراراً، فتراتُ ثقة، اختبارات Welch، cProfile)
105
+ بثمانِ توصيات. **القياسُ على صفحةٍ واقعية قلبَ أولويّاتِه.**
106
+
107
+ ### عنقُ الزجاجة واحدٌ لا ثمانية
108
+
109
+ على صفحةٍ من ٣١٩٥ محرفاً — لا على سلالم اصطناعية:
110
+
111
+ | | تصنيفُ التقرير | القياس/صفحة | لأطروحةٍ من ٣٠٠ صفحة |
112
+ |---|---|---|---|
113
+ | `cer` | حرج | **٢٤٦٢ ms** | **١٢ دقيقة ونصف** ☠️ |
114
+ | `diagnose` | أولوية عالية | ٦٫٥ ms | ٢ ثانية |
115
+ | `repair_text` | أولوية عالية | ٧٫٣ ms | ٢٫٢ ثانية |
116
+ | `decode_glyph_name` | عنق زجاجة | ١٫٢ **µs**/نداء | لا شيء |
117
+
118
+ والمفارقة موجعة: `arafix eval` — الأداةُ المضافة في 0.4.0 **لإثبات
119
+ النزاهة** — كانت غير صالحةٍ للاستعمال على الأطاريح التي بُنيت لأجلها.
120
+
121
+ ### العلاج: مايرز الشعاعية-البِتّية (410×)
122
+
123
+ **خوارزميّ لا تبعيّة.** استُبدلت المصفوفة الكاملة بخوارزمية مايرز
124
+ (1999): تُمثَّل حالةُ عمودٍ كاملٍ بعددٍ صحيح، ويُحسب التالي بعملياتٍ
125
+ بِتّية عليه. وهنا تنقلب قلّةُ حيلة بايثون فضيلةً — أعدادُه غير محدودة
126
+ الدقّة، فعرضُ «الكلمة» عندنا طولُ النمط كلّه لا ٦٤ بِتّاً.
127
+
128
+ cer/صفحة: 2462 ms → 6.1 ms أطروحة: 745 ث → 1.9 ث
129
+
130
+ ومعها اقتطاعُ البادئة واللاحقة المشتركتين (O(n)، وأكثرُ النصّ متطابق).
131
+ **والمسافةُ مضبوطةٌ لا تقريبية.**
132
+
133
+ ### تصنيفُ المحارف: مجموعاتٌ مبنيّةٌ مسبقاً (5.5×)
134
+
135
+ `is_arabic` و`is_presentation_form` تُناديان مرّةً لكل محرف. استُبدل
136
+ بمسح النطاقات فحصُ عضويةٍ في `frozenset` — ١٫٢٤ ms ← ٠٫٢٢ ms على صفحة.
137
+ (قدّر التقرير «١٫٢–٢×»؛ القياس ٥٫٥×.) والمجموعتان مبنيّتان من النطاقات
138
+ **نفسها**، وثمنُهما ٦٤ كيلوبايت لـ١٢٣٢ نقطة. ولا مجموعةَ لـ PUA عمداً:
139
+ ١٣٧ ألف نقطة ثمنٌ لا يشتري شيئاً.
140
+
141
+ diagnose/صفحة: 6.5 ms → 2.3 ms repair_text: 7.3 → 3.3
142
+
143
+ ### الضامن: الذكاءُ يُختبَر بالبلاهة
144
+
145
+ مايرز والمجموعاتُ كلاهما ذكيّ، والسرعةُ بلا ضبطٍ لا قيمة لها. فبقي في
146
+ الشيفرة تنفيذان مرجعيّان بليدان — `levenshtein_reference` و`in_ranges` —
147
+ **لا للاستعمال بل ليكونا محكّاً**:
148
+
149
+ - اختبارٌ عشوائيّ: ٣٠٠٠ زوجٍ ضدّ المرجع + حالاتٌ حديّة.
150
+ - تصنيفُ المحارف مقارَنٌ بالمرجع على **كل نقطة كودٍ في يونيكود**
151
+ (١٬١١٤٬١١٢) — لا على عيّنة.
152
+
153
+ ### ما رُدّ — بالقياس لا بالرأي
154
+
155
+ - **«أضف RapidFuzz» (حرج عندهم)**: قيسَت فكانت أسرع ١١× من مايرز. ورُدّت:
156
+ ١٫٧ ث ← ٠٫١٦ ث لأطروحة، لأحدٍ لا وجود له. **غلطةُ `arafix[ocr]` عينها.**
157
+ - **«مسارٌ سريع في `grapheme_clusters`» (عالٍ عندهم، «١٫٣–٢×»)**: القياس
158
+ يقول **أبطأ** في الحالين (٠٫٢٦←٠٫٢٧ مع التشكيل، ٠٫٢٤٧←٠٫٣١٦ بدونه).
159
+ - **«مسحٌ واحد في `diagnose`» (عالٍ)**: يوفّر ~١ ثانية على أطروحةٍ كاملة.
160
+ ثمنُه ازدواجُ معرفةِ النطاقات — والمجموعاتُ حقّقت المكسب بلا ازدواج.
161
+ - **«Rust/Cython»، «ضبط GC»، «مسار reverse بلا regex»**: نسبُ GC كلها
162
+ ≈١٫٠ (ضوضاء)، والباقي أقلّ من ثانيةٍ للأطروحة.
163
+
164
+ ### وثلاثةُ عيوبٍ في البنشمارك نفسه
165
+
166
+ ١. **يتوّج الخطأ.** «الأسرع: `reverse_visual_line` / `naive[::-1]`» —
167
+ والتقريرُ نفسه يصنّفها `comparable: False` لأنها «تُفسد الأرقام
168
+ ودلالةَ الأقواس». مَن يقيس السرعةَ بلا ضبطٍ يتوّج البليد؛ وهذه
169
+ المكتبةُ قائمةٌ أصلاً على أن `[::-1]` خطأ.
170
+ ٢. **`memory_winner: rapidfuzz peak=0.0`** — صفرُ بايت مستحيل. `tracemalloc`
171
+ لا يرى ما تخصّصه امتداداتُ C خارج مخصِّص بايثون. أثرُ قياسٍ يُعرَض فوزاً.
172
+ ٣. **`detect_mojibake` مقابل «stdlib» مُعلَّمٌ `passed: false`** — وانظر
173
+ المخرجات: نحن نُعطي `السلام`، و«القرين» يُعيد الموجيبيك كما هو. **القرينُ
174
+ أخفق لا نحن**، و`ftfy` وافقنا حرفاً بحرف. وسمُ «اختلاف» بلا قولٍ مَن
175
+ المصيب يقلب النتيجة.
176
+
177
+ الاختبارات: ١٤٢ ← ١٥٥.
178
+
179
+ ## 0.5.0
180
+
181
+ مراجعةٌ خارجية بثمانِ ملاحظات. فُحصت بالتشغيل: **أربعٌ صحّت، وأربعٌ لا.**
182
+
183
+ ### صحّت — والأولى فاضحة
184
+
185
+ **١. الثقة كانت رقماً بلا معنى.** كانت `_confidence` تضرب كلَّ شاهدٍ في
186
+ «كفاية العيّنة»، فتُخرج **٠٫٥٢ لتشخيصٍ قاطع** (فحصُ نطاقٍ حتميّ على
187
+ «ﻣﺮﺣﺒﺎ»)، و**٠٫٤٤ لنصٍّ سليم**. والمكتبة التي تُصدر شهادات ثقة لا
188
+ تُصدَّق إن كانت شهادتُها لا تفرّق بين اليقين والظنّ.
189
+
190
+ القسمة صارت ثلاثيّة، و`Diagnosis.defect_confidence` يفصل ثقةَ كل علّة:
191
+
192
+ | الشاهد | الثقة | لماذا |
193
+ |---|---|---|
194
+ | قاطع (نطاقٌ أو اختبارٌ جبريّ) | **١٫٠ دائماً** | فحصُ نطاقٍ على ٥ محارف قاطعٌ كفحصه على ٥٠٠٠ |
195
+ | ظنّيّ (الاتجاه) | بدرجة شاهده | |
196
+ | «سليم» — شهادةُ نفي | ٠٫٣ ← ٠٫٩ بالحجم | **وحدَها** يحكمها الحجم. وسقفُها ٠٫٩ عمداً: غيابُ العلّة ليس برهانَ سلامة |
197
+
198
+ **٢. `diagnose` كان أعمى بشاهدين من ثلاثة** — والملاحظة صحيحةٌ في
199
+ نتيجتها خاطئةٌ في تعليلها. لم يكن يُضعف شاهد الوصل (فهو يستقبل النصّ
200
+ الخام فيه الأشكال)، بل يُعمي الشاهدين **الحرفيين**: التاء المربوطة
201
+ مخبوءةٌ خلف `U+FE93`، وهي أقواها وزناً (٠٫٥٠). قِسناه: `0.79` بشاهدٍ
202
+ واحد مقابل `0.927` بالثلاثة. فصار `diagnose` يطبّع المفردات داخلياً
203
+ للشواهد الحرفية ويُبقي الخام شاهداً على الوصل — ولا يزال لا يكتب شيئاً.
204
+
205
+ وكشف اختبارُنا الجديد ثغرةً في الإصلاح نفسه: مسارا الموجيبيك والصفحة
206
+ الفارغة كانا يعودان مبكراً فيتخطّيان حاسب الثقة. **لا مخرجَ يتخطّاه الآن.**
207
+
208
+ **٣. حزمة `arafix[ocr]` كانت وعداً بلا سند** — تجرّ `pytesseract` ولا
209
+ كودَ يستعمله. حُذفت. لا نبيع تبعيّةً مقابل نيّة. والدرجة ٤ مُعلَّمة `✗
210
+ غير منفَّذة` في سلّم الـ README نفسه لا في الحواشي.
211
+
212
+ **٤. تغطيةُ «لا تمسّ السليم» والكشيدة ضعيفة.** كان الاختبار جملةً واحدة
213
+ نظيفة — والمحايداتُ والتشكيلُ والكشيدة هي مواطنُ الأذى. صار ٩ حالات
214
+ تشمل الأقواس والتشكيل والنسب والشرطات، و٤ اختباراتٍ للكشيدة.
215
+
216
+ ### لم تصحّ
217
+
218
+ - **«الدرجة ٣ لم تُختبر كفايةً»**: مغطّاة بـ `decode_glyph_name` وبناء
219
+ الخريطة واستخراج الخطوط من PDF حقيقيّ. والقصورُ الحقيقيّ فيها مُعلَنٌ
220
+ سلفاً (خطوط CID) لا مخفيّ.
221
+ - **«التسمية تُضعف الثقة»**: ليست عطباً بل حالةٌ نُشهرها في أول الـ README.
222
+ - **«الدوال المنفصلة خطرة»**: صحيحٌ أن `fold_presentation_forms` ثم
223
+ `fix_order` يُعطب. لكن إخفاءها يُكلّف التركيب، والخطر موثّقٌ في
224
+ الـ docstring و**مُخلَّدٌ اختباراً** (`test_the_bug_reproduced_if_order_is_wrong`).
225
+ و`repair_text` هو المدخل المُوثَّق.
226
+ - **«الاختبارات ملفّان فقط»**: العددُ ليس مقياساً. والقصورُ الحقيقيّ
227
+ (الأعمدة المتعدّدة والجداول) مُعلَنٌ في «حدودٌ مُعلَنة»، و`arafix eval
228
+ --compare` موجودٌ ليقيسه المستعمل على ملفاته.
229
+
230
+ الاختبارات: ١١٩ ← ١٤٢.
231
+
232
+ ## 0.4.0
233
+
234
+ بحثَ مستعملٌ عن الاسم على GitHub فوجد أربعة مستودعات تحمله. قراءتُها
235
+ أفادت أمرين: تضاربُ اسمٍ حقيقيّ، وثغرةٌ في نزاهتنا.
236
+
237
+ ### القياس (`arafix eval`)
238
+
239
+ الأربعةُ كلها تحمل `evaluate` ونحن لم نكن نحمله. وكان قولنا «٠ إخفاق من
240
+ ١٢» مقيساً على ملفاتٍ **ولّدناها بأنفسنا** — أي يقارب الاختبار الدائريّ
241
+ الذي عبناه على أنفسنا في 0.3.0. ثغرةٌ في النزاهة قبل الميزات.
242
+
243
+ - وحدة `arafix.evaluate`: `cer`، `wer`، `levenshtein` (بصفّين لا بمصفوفة:
244
+ الذاكرة O(min) — صفحةُ أطروحة ٣٠٠٠ محرف والمصفوفة تسع ملايين خانة)،
245
+ `evaluate_text`، `evaluate_pdf`، `compare_extractors`.
246
+ - `arafix eval file.pdf --truth truth.txt --compare` يقيس **كل المسارات
247
+ على ملفك أنت** ويرتّبها. أوّلُ رقمٍ لنا::
248
+
249
+ pymupdf CER 0.00% WER 0.00%
250
+ mupdf-bidi CER 30.51% WER 48.48%
251
+
252
+ - بلا تبعيّات (لا `jiwer`)، ويعدّ الترقيم والتشكيل افتراضياً: تجاهلُهما
253
+ يرفع الدرجة كذباً ويمحو أصدق ما يقيسه الاختبار.
254
+ - يسرد أسوأ السطور: المعدّل يقول «٣٪ خطأ» ولا يقول أين.
255
+
256
+ ### التسمية
257
+
258
+ `arafix` شاغرٌ على PyPI، **مأخوذٌ على GitHub بأربعة مستودعات**، وأحدها
259
+ (`AraFix-V3.0`) يوفّر حزمةً عليا اسمها `arafix` بالحرف. التضارب حقيقيّ
260
+ لا نظريّ: من ثبّت الاثنين، آخرُهما يفوز بـ `import arafix` صامتاً.
261
+ أُضيف تنبيهٌ في الـ README وقسمُ «التسمية والجيران». بدائل شاغرة:
262
+ `warraq` (الورّاق)، `qirtas`، `raqim`، `tanqih`.
263
+
264
+ ### الجيران
265
+
266
+ - ثلاثةٌ من الأربعة مشروعٌ واحد (V1→V3): غلافُ نموذج HuggingFace يصحّح
267
+ التشكيل والأخطاء الصوتية. لا تقاطع.
268
+ - `CAMeL-Lab/arafix_ocr` (NYU أبوظبي): تصحيحُ OCR بـ n-gram. **متكامل لا
269
+ منافس** — يبدأ من حيث تنتهي درجتُنا الرابعة.
270
+ - ما زال ينقصنا: نموذجٌ لغويّ يحسم «المجالت» بدل المعجم الساذج، وإخراجُ
271
+ PDF قابل للبحث.
272
+
273
+ الاختبارات: ١٠٦ ← ١١٩.
274
+
275
+ ## 0.3.0
276
+
277
+ سأل مستعملٌ سؤالاً بسيطاً: «هل تبقى الأقواس حول كلمتها أم ينقلب أحدها؟»
278
+ فكشف السؤالُ **ثلاثة أعطاب**، لا واحداً.
279
+
280
+ ### قياسٌ أوّلاً
281
+
282
+ على ١٢ سطراً فيها ترقيم، مرّرناها في ملف PDF حقيقيّ:
283
+
284
+ | المسار | الإخفاق |
285
+ |---|---|
286
+ | `get_text()` — بِدي MuPDF | ٩ من ١٢ |
287
+ | القراءة الهندسية + arafix | ٠ من ١٢ |
288
+
289
+ الحروف تخرج سليمةً والمحايدات مبعثرة::
290
+
291
+ (مقدمة الدراسة) → ()مقدمة الدراسة
292
+ الفقرة [أ-ج] هنا → ج[ هنا-الفقرة ]أ
293
+ توقف! → !توقف
294
+
295
+ و«؟» و«؛» تنجوان وحدهما — لأنهما عربيّتان (صنف AL) لا محايدتين.
296
+
297
+ ### العطب ١: بِدي المحرّك يبعثر المحايدات
298
+
299
+ - محرّك PyMuPDF صار يقرأ **تيار الرسم الخام** ومواضع الجليفات، ويترك
300
+ الدرجة ٢ تعكس بمنطقنا (`bidi="geometry"`، وهو الافتراضيّ الآن).
301
+ - ونقرأ بـ `get_texttrace` لا `rawdict`: الأخير يعيد ترتيب محارفه ببِدي
302
+ MuPDF قبل تسليمها، فلا تيارَ فيه أصلاً (أوّلُ محرفٍ يأتي من أقصى اليمين).
303
+ - ولكلٍّ من ربط العنقود وترتيبه شاهدٌ مختلف: **الربط من التيار** (الهندسة
304
+ تكذب: العلامة عرضُها صفر فتُرسَم عند القلم بعد أن تجاوز حرفَها، فـ x
305
+ عندها هو x للحرف التالي)، **والترتيب من الهندسة**.
306
+ - `bidi="mupdf"` باقٍ للمقارنة.
307
+
308
+ ### العطب ٢: العكس كان على المحارف لا العناقيد
309
+
310
+ أولاً → أوًلا ثانياً. → ثانيا.ً
311
+
312
+ علامة التشكيل عرضُها صفر وتشترك في موضع حرفها. أُضيف `grapheme_clusters`
313
+ وصار `reverse_visual_line` يعكس العناقيد ويحفظ داخلها (`cluster_aware`).
314
+
315
+ ### العطب ٣: أشكال التشكيل الفاصلة كانت تُطبَّع مبكراً
316
+
317
+ نُشرت → نشُرت المتغيّر → المتغيرّ
318
+
319
+ `U+FE79` («ضمّةٌ بشكلٍ فاصل») فئتها `Lo` لا `Mn` — محرفٌ قائم بذاته.
320
+ وتطبيعُها يحيلها علامةً لاصقة، فتنقلب وحدةُ العكس. جريمةُ الرباط نفسها
321
+ بثوبٍ آخر.
322
+
323
+ - استُبدل بمعيار «طول التفكيك» معيارُ **تغيُّر بنية العنقود**
324
+ (`_changes_cluster_structure`). ومعيار الطول كان يُعمي عن هذا الصنف:
325
+ تفكيك `U+FE79` هو [كشيدة + ضمّة]، ونحن نطرح الكشيدة فيعود الطول واحداً.
326
+ - `DEFERRED_PF_TO_BASE` (٤٨٩) = رباطات (٤٨٣) + تشكيلٌ فاصل (٦).
327
+ - `expand_ligatures` → `expand_deferred_forms` (الاسم القديم باقٍ للتوافق).
328
+
329
+ ### وأعطابٌ صغرى كشفها السؤال نفسه
330
+
331
+ - `GDP_2024` كان يخرج `2024_GDP`: الشرطة السفلية لم تكن في المقطع اللاتينيّ.
332
+ - «توقف!» لم تكن تُعكس: حارسُ كفاية العيّنة كان يردّ **البرهان** كما يردّ
333
+ الإحصاء. وهويّة الوصل برهانٌ، والبرهان لا يحتاج عيّنة — فأُعفي وحده.
334
+
335
+ ### الأقواس: الجواب
336
+
337
+ تبقى حول كلمتها، **بشرط المرآة**. القوس يُخزَّن بجليفه المرسوم، وجليفُ
338
+ أقصى اليسار في سطرٍ عربيّ هو «(» وإن كان المحرف المنطقيّ هناك «)». فالعكس
339
+ وحده يعطي «)مقدمة(». والمرآة تشمل `() [] {} <> «» “”` — ولا تشمل `؟ ؛ ! .
340
+ , - _` فليست مِرآتية، ومرآتُها تخريب.
341
+
342
+ الاختبارات: ٦٩ ← ١٠٦.
343
+
344
+ ## 0.2.0
345
+
346
+ ### إصلاح عطبٍ جوهريّ: انقلاب رباط لام-ألف
347
+
348
+ بلّغ مستعملٌ أن كلماتٍ تخرج هكذا: `االنترنيت`، `المجالت`، `األطاريح`،
349
+ `اإلجراء`. العطب كان **في المكتبة**، ونتيجةً مباشرة لقرارها المعماري
350
+ الخامس («التطبيع قبل الاتجاه»).
351
+
352
+ **السبب.** «ﻻ» جليفٌ واحد في PDF — والرباط في العربية إلزاميّ لا
353
+ اختياريّ. كانت الدرجة ١ تفكّه إلى حرفين، ثم تعكس الدرجةُ ٢ السطرَ
354
+ فتعكس الحرفين معه: «لا» ← «ال».
355
+
356
+ **الإصلاح.**
357
+
358
+ - شُطِّر التطبيع حول الاتجاه: `١أ مفردات ← ٢ اتجاه ← ١ب رباطات`.
359
+ الرباط يبقى ذرّةً حتى يستقرّ الترتيب. (`fold_simple_forms`،
360
+ `expand_ligatures`)
361
+ - قُسِم `PF_TO_BASE` إلى `SIMPLE_PF_TO_BASE` (٢٤٨) و`LIGATURE_PF_TO_BASE`
362
+ (٤٨٣) بمعيارٍ **مشتقّ** لا مكتوبٍ بيد: طولُ التفكيك. فالعطب لم يكن في
363
+ لام-ألف وحدها بل في ٤٨٣ رباطاً.
364
+
365
+ ### عطبٌ ثانٍ انكشف تحت الأول: عمى الكاشف بعد التطبيع
366
+
367
+ التطبيع يمحو صيغ الوصل، وهي شاهدُ كاشف الاتجاه. فكان يفتح عيناً
368
+ (التاء المربوطة) ويفقأ أخرى.
369
+
370
+ - صار `detect_visual_order` يقبل `shaped_source=` فيقرأ الطبقتين معاً.
371
+ - استُبدل بفحص طرفَي الكلمة **هويّةُ الوصل**:
372
+ `joins_forward(a) == joins_backward(b)` لكل متجاورين — لا تتخلّف في
373
+ نصٍّ منطقيّ، فخرقُها برهانُ انعكاس. شاهدٌ لا تماثليّ: يدحض ولا يُزكّي.
374
+ (الفحص القديم كان يُفلت كلماتٍ كـ«الإجراء».)
375
+
376
+ ### جديد
377
+
378
+ - `arafix.lamalef` — ترقيع رجعيّ لنصٍّ أعطبته أداةٌ أخرى:
379
+ قاطعٌ حتميّ (ألفان متجاورتان) + مدخل `lexicon=` للمُبهَم.
380
+ - `Defect.LAM_ALEF_TRANSPOSED`، `Stage.EXPAND_LIGATURES`،
381
+ `Stage.REPAIR_LAM_ALEF`، `PipelineConfig.lexicon`.
382
+
383
+ ### سبب فوات العطب
384
+
385
+ مولّد العيّنات لم يكن يُنتج رباطات — أي أن ٤٥ اختباراً خضراء كانت
386
+ تختبر المكتبة على عالمٍ لا «لا» فيه. أُضيف `ligate()` إلى المولّد،
387
+ وأُضيفت الكلمات الأربع المُبلَّغ عنها اختباراتٍ دائمة، وفخاخٌ يجب ألّا
388
+ تُمسّ («أفعالهم لا تطابق أقوالهم»، «قال»، «جمال»، «لآلئ»).
389
+
390
+ الاختبارات: ٤٠ ← ٦٩. الـ doctests: ٦ ← ١٠.
391
+
392
+ ## 0.1.0
393
+
394
+ الإصدار الأول. الدرجات ٠–٣.
@@ -0,0 +1,34 @@
1
+ # Contributing
2
+
3
+ Thanks for caring about Arabic text integrity.
4
+
5
+ ## Highest-value contributions (in order)
6
+
7
+ 1. **PDFs that break us** — with a short note of expected vs actual text
8
+ 2. **Layout edge cases** — multi-column, tables, headers
9
+ 3. **New extractors** — implement `Extractor` + `@register`
10
+ 4. **Stronger order signals** — pure functions in `diagnose.py`
11
+ 5. **Docs** — English or Arabic clarifications
12
+
13
+ ## Dev setup
14
+
15
+ ```bash
16
+ pip install -e ".[dev]"
17
+ pytest
18
+ ruff check src tests examples
19
+ ```
20
+
21
+ ## Rules of the house
22
+
23
+ - **Do not invent characters** (especially CID fonts). Prefer explicit failure.
24
+ - **Do not “fix just in case.”** Every stage needs a signal.
25
+ - Prefer a new **named test** that documents a *decision*, not a line of code.
26
+ - Core stays **dependency-free**. Optional extras only.
27
+
28
+ ## PR tips
29
+
30
+ - Keep diffs focused.
31
+ - Add CHANGELOG notes under a future version heading if you touch user-visible behavior.
32
+ - Run the suite before pushing.
33
+
34
+ License: MIT (see [LICENSE](LICENSE)).
arafix-0.8.0/DEPLOY.md ADDED
@@ -0,0 +1,127 @@
1
+ # Deploy checklist — arafix 0.8.0 → PyPI
2
+
3
+ You are shipping an **Alpha**. That is honest and correct. This file is the
4
+ short path from “green on your machine” to “live on PyPI.”
5
+
6
+ ## Pre-flight (already done in-tree)
7
+
8
+ - [x] Version `0.8.0` in `pyproject.toml` and `src/arafix/__init__.py`
9
+ - [x] `CHANGELOG.md` has `## 0.8.0`
10
+ - [x] Tests pass (`pytest`)
11
+ - [x] `ruff check src tests examples`
12
+ - [x] `python -m build` + `twine check --strict dist/*`
13
+ - [x] English blurb on README + English `description`
14
+ - [x] `src/arafix/py.typed` (PEP 561)
15
+ - [x] MIT license, classifiers, CLI entry point, optional extras
16
+
17
+ ## Your steps (need your accounts)
18
+
19
+ ### 1) GitHub repo
20
+
21
+ ```text
22
+ https://github.com/bio-colab/arafix (public)
23
+ ```
24
+
25
+ Push this tree to `main`. Ensure workflows exist:
26
+
27
+ - `.github/workflows/ci.yml`
28
+ - `.github/workflows/publish.yml`
29
+
30
+ ### 2) GitHub Environments
31
+
32
+ *Settings → Environments → New environment*
33
+
34
+ | Name | Required reviewers |
35
+ |---|---|
36
+ | `testpypi` | optional |
37
+ | `pypi` | **yes — you** |
38
+
39
+ ### 3) Pending publishers (PyPI + TestPyPI)
40
+
41
+ [PyPI → Publishing → Add pending publisher](https://pypi.org/manage/account/publishing/)
42
+
43
+ | Field | Value |
44
+ |---|---|
45
+ | Project name | `arafix` |
46
+ | Owner | `bio-colab` |
47
+ | Repository | `arafix` |
48
+ | Workflow name | `publish.yml` |
49
+ | Environment | `pypi` |
50
+
51
+ Repeat on [test.pypi.org](https://test.pypi.org) with environment `testpypi`.
52
+
53
+ > **Critical:** a pending publisher does **not** reserve the name. Configure
54
+ > then publish the same day.
55
+
56
+ ### 4) Local smoke (optional but smart)
57
+
58
+ ```bash
59
+ cd arafix # package root (where pyproject.toml lives)
60
+ python -m pip install -e ".[dev]"
61
+ pytest
62
+ ruff check src tests examples
63
+ python -m build
64
+ python -m twine check --strict dist/*
65
+ ```
66
+
67
+ ### 5) TestPyPI
68
+
69
+ GitHub Actions → **publish** → Run workflow → target: **testpypi**
70
+
71
+ Then:
72
+
73
+ ```bash
74
+ pip install --index-url https://test.pypi.org/simple/ \
75
+ --extra-index-url https://pypi.org/simple/ \
76
+ arafix
77
+ arafix --version
78
+ python -c "from arafix import repair_text; print(repair_text('\ufee3\ufeae\ufea3\ufe92\ufe8e').text)"
79
+ ```
80
+
81
+ ### 6) Real PyPI
82
+
83
+ ```bash
84
+ # versions already 0.8.0 — bump only if you change code after TestPyPI
85
+ git tag v0.8.0
86
+ git push origin main --tags
87
+ gh release create v0.8.0 --title "0.8.0" --notes-file CHANGELOG.md
88
+ ```
89
+
90
+ Approve the `pypi` environment in Actions. Done.
91
+
92
+ ### 7) After live
93
+
94
+ ```bash
95
+ pip install -U "arafix[pdf]"
96
+ arafix --version
97
+ ```
98
+
99
+ Update the README badge if you add a PyPI version shield:
100
+
101
+ ```markdown
102
+ [![PyPI](https://img.shields.io/pypi/v/arafix.svg)](https://pypi.org/project/arafix/)
103
+ ```
104
+
105
+ ## What not to claim on the PyPI page
106
+
107
+ - “Full OCR for scanned PDFs” — stage 4 is not shipped
108
+ - “Perfect every multi-column magazine” — layout is heuristic
109
+ - “Production-stable 1.0” — classifier is Alpha
110
+
111
+ Do claim: graded Arabic recovery, zero-dep core, evidence-based diagnosis,
112
+ optional PDF + layout.
113
+
114
+ ## If something fails
115
+
116
+ | Symptom | Fix |
117
+ |---|---|
118
+ | Twine rejects README | Ensure README renders; re-run `twine check --strict` |
119
+ | OIDC / publisher error | Workflow name must be exactly `publish.yml` |
120
+ | Version already exists | Bump to `0.8.1` in both files + CHANGELOG |
121
+ | Name taken | See RELEASING.md rename options (`warraq`, …) |
122
+
123
+ ## One-liner philosophy for the release notes
124
+
125
+ > arafix 0.8 recovers broken Arabic from native PDFs: diagnose with evidence,
126
+ > repair presentation forms / visual order / lam-alef, clean PDF artifacts,
127
+ > and read multi-column pages right-to-left — without a fake OCR dependency.