Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ The format follows Keep a Changelog, and releases will follow Semantic Versionin

## [Unreleased]

### Added

- `ArabicMatchRange` with original UTF-16 source offsets.
- `ArabicSearch.findFirst(...)` and `ArabicSearch.findAll(...)`.
- Optional overlapping matches and result limits.
- Offset-aware normalization across diacritics, Quranic marks, tatweel, alef variants, whitespace collapse, and Latin lowercase expansion.
- Android background highlighting with `ArabicHighlighter`.
- Range-search coverage in the sample app and unit tests.

## [0.1.0] - 2026-08-02

### Added
Expand Down
46 changes: 41 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# ArabicKit

Production-ready Kotlin utilities for Arabic text normalization, search, and numeral conversion on Android.
Production-ready Kotlin utilities for Arabic text normalization, search, range mapping, highlighting, and numeral conversion on Android.

> ArabicKit `0.1.0` is the first public foundation release. The API is tested, but `0.x` releases may still evolve before the first stable release.

Expand All @@ -11,6 +11,9 @@ Production-ready Kotlin utilities for Arabic text normalization, search, and num
- Normalize alef variants and alef maksura.
- Normalize whitespace and Latin casing.
- Search through normalized Arabic text.
- Return match ranges in the original UTF-16 source text.
- Find first, multiple, or overlapping normalized matches.
- Highlight normalized matches with Android `SpannableString`.
- Convert Western, Arabic-Indic, and Eastern Arabic-Indic digits.
- Java-friendly APIs through `@JvmStatic` and `@JvmOverloads`.
- A dependency-free Android sample app.
Expand All @@ -35,16 +38,45 @@ On Windows:

## Quick usage

### Normalize Arabic text

```kotlin
val normalized = ArabicNormalizer.normalize("إِنَّ الــلَّهَ غَفُورٌ")
// "ان الله غفور"
```

### Find original-text ranges

```kotlin
val source = "إِنَّ اللَّهَ غَفُورٌ، إن الله رحيم"

val matches = ArabicSearch.contains(
text = "إِنَّ اللَّهَ غَفُورٌ",
val ranges = ArabicSearch.findAll(
text = source,
query = "ان الله",
)
// true

ranges.forEach { range ->
println(source.substring(range.start, range.endExclusive))
}
```

The returned offsets target the original source, including its diacritics and tatweel, so they can be used directly with `substring` or Android spans.

### Highlight matches on Android

```kotlin
val highlighted = ArabicHighlighter.highlightBackground(
text = source,
query = "ان الله",
color = Color.YELLOW,
)

textView.text = highlighted
```

### Convert numerals

```kotlin
val western = ArabicNumerals.convert(
input = "الإصدار ٢٠٢٦",
target = ArabicNumeralSystem.WESTERN,
Expand All @@ -59,9 +91,13 @@ val western = ArabicNumerals.convert(
- Java bytecode target 17
- Kotlin support is provided by Android Gradle Plugin built-in Kotlin.

## Releases

The first public release is `v0.1.0`. Binary AAR assets are available from GitHub Releases.

## Project status

ArabicKit is preparing the public `0.1.0` release. See [ROADMAP.md](ROADMAP.md) and [CHANGELOG.md](CHANGELOG.md).
ArabicKit is developing `0.2.0`, focused on original-text search ranges and Android highlighting. See [ROADMAP.md](ROADMAP.md), [CHANGELOG.md](CHANGELOG.md), and the [0.2.0 implementation plan](docs/0.2.0-search-ranges-plan.md).

## Contributing

Expand Down
36 changes: 34 additions & 2 deletions README_AR.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# ArabicKit — العربية

مكتبة Kotlin إنتاجية لمعالجة النص العربي والبحث وتحويل الأرقام داخل تطبيقات Android.
مكتبة Kotlin إنتاجية لمعالجة النص العربي والبحث وإرجاع مواقع النتائج والتظليل وتحويل الأرقام داخل تطبيقات Android.

> الإصدار `0.1.0` هو أول إصدار تأسيسي عام للمكتبة. الواجهات الحالية مختبرة، لكن إصدارات `0.x` قد تتطور قبل الوصول إلى أول إصدار مستقر.

Expand All @@ -11,6 +11,9 @@
- توحيد أشكال الألف والألف المقصورة.
- تنظيم المسافات وحالة الأحرف اللاتينية.
- البحث بعد التطبيع.
- إرجاع مواقع النتائج داخل النص الأصلي باستخدام فهارس UTF-16.
- العثور على أول نتيجة أو جميع النتائج أو النتائج المتداخلة.
- تظليل النتائج داخل Android باستخدام `SpannableString`.
- التحويل بين الأرقام الغربية والعربية والهندية الشرقية.
- واجهات سهلة للاستخدام من Kotlin وJava.
- تطبيق عينة مبني بـXML وViewBinding.
Expand All @@ -24,9 +27,38 @@
.\gradlew.bat verifyFoundation
```

## مثال: إرجاع مواقع النتائج داخل النص الأصلي

```kotlin
val source = "إِنَّ اللَّهَ غَفُورٌ، إن الله رحيم"

val ranges = ArabicSearch.findAll(
text = source,
query = "ان الله",
)

ranges.forEach { range ->
println(source.substring(range.start, range.endExclusive))
}
```

المواقع الناتجة تشير إلى النص الأصلي المشكّل، وليست إلى النسخة المطبّعة، ولذلك يمكن استخدامها مباشرة مع `substring` أو Android spans.

## مثال: تظليل النتائج

```kotlin
val highlighted = ArabicHighlighter.highlightBackground(
text = source,
query = "ان الله",
color = Color.YELLOW,
)

textView.text = highlighted
```

## حالة المشروع

المشروع يجهّز الآن الإصدار العام الأول `0.1.0`. قد تتطور واجهات `0.x` قبل الوصول إلى الإصدار المستقر الأول.
تم نشر الإصدار العام الأول `0.1.0`. يجري الآن تطوير `0.2.0` لإضافة مواقع البحث الأصلية والتظليل على Android.

## الرخصة

Expand Down
12 changes: 7 additions & 5 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@
- [x] Numeral conversion.
- [x] Unit tests and CI.
- [x] API review.
- [ ] Publish the first public release.
- [x] Publish the first public release.

## 0.2.0 — Search ranges and highlighting

- Original-text index mapping.
- Safe match ranges.
- Android `Spannable` highlighting.
- Multi-match support.
- [x] Original-text index mapping.
- [x] Safe match ranges.
- [x] Android `Spannable` highlighting.
- [x] Multi-match and overlapping-match support.
- [x] Sample and documentation updates.
- [ ] Complete CI review and publish `v0.2.0`.

## 0.3.0 — Advanced Arabic utilities

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package io.github.khalid567cpu.arabickit

import android.text.SpannableString
import android.text.Spanned
import android.text.style.BackgroundColorSpan

/**
* Android text-highlighting helpers backed by [ArabicSearch] ranges.
*/
public object ArabicHighlighter {
/**
* Finds all normalized matches and applies a background-color span to each.
*/
@JvmStatic
@JvmOverloads
public fun highlightBackground(
text: CharSequence,
query: CharSequence,
color: Int,
options: ArabicNormalizationOptions = ArabicNormalizationOptions(),
allowOverlaps: Boolean = false,
): SpannableString = applyBackground(
text = text,
ranges = ArabicSearch.findAll(
text = text,
query = query,
options = options,
allowOverlaps = allowOverlaps,
),
color = color,
)

/**
* Applies a background-color span to precomputed original-text [ranges].
*/
@JvmStatic
public fun applyBackground(
text: CharSequence,
ranges: Iterable<ArabicMatchRange>,
color: Int,
): SpannableString {
val highlighted = SpannableString(text)

ranges.forEach { range ->
require(range.endExclusive <= text.length) {
"Match range $range exceeds source length ${text.length}."
}

if (range.length > 0) {
highlighted.setSpan(
BackgroundColorSpan(color),
range.start,
range.endExclusive,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE,
)
}
}

return highlighted
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.github.khalid567cpu.arabickit

/**
* A match range expressed in UTF-16 offsets of the original source text.
*
* The [start] offset is inclusive and [endExclusive] is exclusive, matching
* Android `Spannable` and Kotlin substring conventions.
*/
public data class ArabicMatchRange(
public val start: Int,
public val endExclusive: Int,
) {
init {
require(start >= 0) { "start must be non-negative." }
require(endExclusive >= start) { "endExclusive must be greater than or equal to start." }
}

/** Number of UTF-16 code units covered by this range. */
public val length: Int
get() = endExclusive - start

/** Returns this range as a Kotlin [IntRange]. */
public fun toIntRange(): IntRange = start until endExclusive
}
Original file line number Diff line number Diff line change
@@ -1,46 +1,15 @@
package io.github.khalid567cpu.arabickit

import java.util.Locale
import io.github.khalid567cpu.arabickit.internal.ArabicNormalizationEngine

/**
* Deterministic Arabic text normalization for search and comparison.
*/
public object ArabicNormalizer {
private val arabicDiacritics = Regex("[\\u064B-\\u065F\\u0670]")
private val quranicAnnotationMarks = Regex("[\\u0610-\\u061A\\u06D6-\\u06ED]")
private val alefVariants = Regex("[\\u0622\\u0623\\u0625\\u0671]")
private val whitespace = Regex("\\s+")

@JvmStatic
@JvmOverloads
public fun normalize(
input: CharSequence,
options: ArabicNormalizationOptions = ArabicNormalizationOptions(),
): String {
var result = input.toString()

if (options.removeDiacritics) {
result = result.replace(arabicDiacritics, "")
}
if (options.removeQuranicMarks) {
result = result.replace(quranicAnnotationMarks, "")
}
if (options.removeTatweel) {
result = result.replace("\u0640", "")
}
if (options.normalizeAlef) {
result = result.replace(alefVariants, "\u0627")
}
if (options.normalizeAlefMaksura) {
result = result.replace('\u0649', '\u064A')
}
if (options.normalizeWhitespace) {
result = result.replace(whitespace, " ").trim()
}
if (options.lowercaseLatin) {
result = result.lowercase(Locale.ROOT)
}

return result
}
): String = ArabicNormalizationEngine.normalize(input, options).text
}
Loading
Loading