kashida_core

Pure Dart library for kashida (tatweel, U+0640) in Arabic and Syriac text: compile a small pattern language, find insertion points and priorities, insert a fixed count, or wrap and fill a target width with a caller-supplied measure callback.

No Flutter dependency. For TextPainter measuring and the KashidaText widget, use kashida, which re-exports this package.

Features

  • Compile pattern text, including use of a built-in set
  • Built-ins: arabic-naskh, arabic-nastaliq, arabic-simple, syriac
  • Find points (findKashidaPoints) with optional bare-tatweel stripping (mark-seated tatweels are kept)
  • Insert a fixed tatweel count at every allowed join (insertKashida)
  • layoutParagraph — greedy wrap and fill by priority given width and measure
  • Optional justifyLastLine — stretch the last line of each paragraph (off by default)
  • Grapheme-cluster indices, joining analysis, and rasm folding

Getting started

dependencies:
  kashida_core: ^0.1.2
import 'package:kashida_core/kashida_core.dart';

Usage

Find points or insert a fixed count

final set = requiredBuiltinPatternSet('arabic-simple');

final found = findKashidaPoints('بيت', set);
for (final point in found.points) {
  print('${point.priority} @ grapheme ${point.index}');
}

// Same count at every allowed join — does not wrap or honor a width.
print(insertKashida('بيت', set));

KashidaPoint.index is a grapheme index. Do not pass it to substring; use point.endOffsetIn(text) or insertKashidaAt.

findKashidaPointsIn (also named findKashidaPointsPatterns) skips stripping bare tatweel.

Justify to a width

final lines = layoutParagraph(
  paragraph,
  set,
  width: 320,
  measure: (line) => /* width of line in the same units as 320 */,
  // justified: true,           // default
  // justifyLastLine: false,    // default — last line stays short
);

Each \n starts a new paragraph with its own last line. When justified: false, no line stretches and justifyLastLine is ignored.

Custom rules

final set = compilePatternText('''
use arabic-naskh
* 2 @Heh .
''');

Pattern grammar details: raqim-kashida README.

Built-in pattern sets

Name Intended for
arabic-naskh Classical naskh and naskh-like faces
arabic-nastaliq Nastaliq (naskh rules plus nastaliq tailoring)
arabic-simple Simple / kufic-style faces (Microsoft-style newspaper rules)
syriac Syriac, following the LibreOffice / expert guidelines

requiredBuiltinPatternSet(name) throws if unknown; builtinPatternSet(name) returns null instead.

Layout behavior

  • Greedy wrap on whitespace; a word wider than the target overflows
  • Leftover thinner than one tatweel → KashidaLine.unusedWidth
  • Last line short unless justifyLastLine: true

Design notes: doc/design.md.

Regenerating Unicode tables

Joining type/group tables are generated from Unicode 17.0.0 UCD files:

dart run tool/generate_joining_tables.dart

Downloads are cached under tool/ucd/ (gitignored). Generated joining_tables.dart is committed; CI does not hit the network to regenerate.

Acknowledgements

Port of raqim-kashida by Khaled Hosny and Alif Type. Any mistakes in the Dart implementation are ours.

Additional information

Issues: github.com/byshy/kashida/issues. Include a small Arabic or Syriac sample and the pattern set name when reporting matching or layout bugs.

To publish a new version from this monorepo subdirectory:

./tool/publish.sh

(dart pub publish directly under kashida_core/ fails while the package lives in a git subdirectory; the script publishes from a clean temporary tree.)

Libraries

kashida_core
Finding kashida (tatweel) insertion points and priorities, driven by a small pattern language.