bangla_pdf 1.1.1 copy "bangla_pdf: ^1.1.1" to clipboard
bangla_pdf: ^1.1.1 copied to clipboard

Correct Bangla (Bengali) text in PDFs: real OpenType shaping for conjuncts, reph and vowel signs, with text that copies back out as clean Unicode.

bangla_pdf

Correct Bangla in PDFs.
Real OpenType shaping for conjuncts, reph and vowel signs — and text that copies back out as clean Unicode.

pub package pub points license platforms

Stand With Palestine


package:pdf has no complex-script shaping, so Bangla comes out broken: conjuncts fall apart, ি land on the wrong side of their consonant, and reph (র্) goes anywhere but where it belongs.

bangla_pdf shapes Bangla with the font's own OpenType GSUB/GPOS tables, in pure Dart — no FFI, no native toolchain, works on web. Drop in Text() and it just works.

Before and after

🧬 Real shaping The font's own GSUB/GPOS, not a lookup table. Conjuncts, reph, phalas and pre-base matras all correct.
📋 Copy-pasteable The PDF holds real Unicode. pdftotext, search and screen readers get your original string back.
🎯 Zero config No initialisation, no asset bundling. A Bangla font ships with the package.
🪶 Pure Dart No FFI, no C toolchain, no native build step — so it works on web too.
🔒 Drop-in Nothing removed from the 1.0.x API, and the bundled typeface is unchanged.
🧪 Verified Diffed glyph-by-glyph against HarfBuzz: 234/234 on three fonts.

🚀 Quick start #

dependencies:
  bangla_pdf: ^1.1.1
import 'package:bangla_pdf/bangla_pdf.dart';
import 'package:pdf/widgets.dart' as pw;

final pdf = pw.Document();

pdf.addPage(
  pw.Page(
    build: (context) => Text('আমার সোনার বাংলা, আমি তোমায় ভালোবাসি।'),
  ),
);

final bytes = await pdf.save();

No initialisation, no font registration, no asset bundling. A Bangla font ships with the package and is used automatically.


✨ What it fixes #

before (1.0.6) now
কর্ম ধর্ম বর্ষ পূর্ব শর্ত threw RangeError, killed pdf.save() render correctly
ক্ষ্ম ঙ্ক্ষ ত্ত্ব চ্ছ্ব ম্ভ্র স্ত্র্য base + a stray hasanta ligated
copy/paste, search, pdftotext Avgvi ‡mvbvi evsjv the original Unicode
Bengali digits ০–৯ silently became 0–9 preserved
silently became $ preserved
missing glyph rendered
র‍্য vs র্য (ZWJ) ZWJ discarded, both identical distinguished
NFC vs NFD input mostly equivalent identical by construction

Measured, not asserted — every row is checked by the test suite.

📋 The text in the PDF is real Bangla #

Run pdftotext over a document generated by each version. 1.0.6:

$ pdftotext invoice.pdf -
Avgvi ‡mvbvi evsjv , Avwg ‡Zvgvq fv‡jvevwm| ¶&g O&¶ Ë&e ”Q&e g&å

Not one Bengali codepoint — the PDF stored Bijoy ANSI, so copy, search, grep and screen readers all got mojibake. 1.1.0:

$ pdftotext invoice.pdf -
আমার সোনার বাংলা, আমি তোমায় ভালোবাসি।
ক্ষ্ম ঙ্ক্ষ ত্ত্ব চ্ছ্ব ম্ভ্র স্ত্র্য

Byte-for-byte the string you passed to Text().


🧩 Widgets #

Every widget takes a plain Dart String in logical order and handles the rest.

Text('বাংলা টেক্সট')
Header('বাংলা শিরোনাম')
Paragraph('একটি অনুচ্ছেদ।')

BulletList(items: ['প্রথম আইটেম', 'Second item', 'তৃতীয় আইটেম'])

Table(data: [
  ['পণ্য',   'পরিমাণ', 'মূল্য'],
  ['কফি',    '২',      '৳২০'],
  ['বিস্কুট', '৩',      '৳১৫'],
])

RichText(spans: [
  TextSpan('বাংলা বোল্ড ', fontWeight: pw.FontWeight.bold),
  TextSpan('এবং সাধারণ'),
])

A page using every widget

Every widget on one page — all shaped, all selectable.

Mixed scripts need no special handling — Latin, Bengali digits and currency all render in one pass:

Text('Invoice #1042 - মোট ৳১২,৫০০.০০ - তারিখ ০১/০৯/২০২৬')
Full Text parameters
Text(
  'বাংলা',
  fontSize: 16,
  fontWeight: pw.FontWeight.normal,
  color: PdfColors.black,
  textAlign: pw.TextAlign.start,
  maxLines: 3,
  banglaFont: myFont,               // font for Bangla runs
  style: pw.TextStyle(...),         // non-Bangla runs
  banglaStyle: pw.TextStyle(...),   // Bangla runs
)

🔤 Using your own font #

final font = BanglaPdf.loadFont(
  await rootBundle.load('assets/fonts/SolaimanLipi.ttf'),
);

Text('বাংলা', banglaFont: font);          // one widget
BanglaPdf.configure(defaultFont: font);   // everywhere

BanglaPdf.loadFont returns a font that shapes with its own OpenType tables and can draw conjunct glyphs no codepoint maps to. A plain pw.Font.ttf still works but is treated as a legacy 8-bit font.


⚙️ Shaping modes #

BanglaPdf.configure(shapingMode: BanglaShapingMode.auto);
mode behaviour
auto (default) shape with the font's OpenType tables; fall back to legacy for a font with no Bengali coverage
unicode always shape, even if the font looks unsuitable
legacy the 1.0.x Bijoy ANSI pipeline, for byte-identical old output

📚 API #

symbol purpose
Text AutoText Header Paragraph RichText TextSpan BulletList Table widgets
BanglaPdf.configure({shapingMode, defaultFont}) package-wide settings
BanglaPdf.loadFont(ByteData) load a Unicode Bangla font
BanglaPdf.covers(font, text) can this font draw this string?
BanglaPdf.defaultFont · .shapingMode · .reset() current configuration
BanglaShapingMode auto · unicode · legacy
String.shapeForPdf(font)ShapedRun glyphs, advances, offsets and cluster text, for callers drawing their own content
BanglaFontManager().defaultFont · .legacyFont the bundled fonts
String.fix · String.isBanglaText 1.0.x helpers, unchanged

🔬 How it works #

  1. Normalise — NFC and NFD are reconciled, two-part vowels ( ) are split the way font rules expect, and nukta pairs are composed.
  2. Segment the text into Indic syllables.
  3. Find the base consonant by asking the font which consonants it gives below-base or post-base forms to.
  4. Reorder — pre-base matras move in front of the cluster; reph moves after the base and any below-base form.
  5. Apply GSUB in the OpenType Indic order — nukt akhn rphf blwf half pstf vatu cjct, then pres abvs blws psts haln, then calt clig rclt rlig — each under the right per-glyph mask.
  6. Apply GPOSdist abvm blwm mark mkmk kern, including mark-to-base, mark-to-ligature and mark-to-mark attachment.
  7. Emit PDF text as a Type0/Identity-H CID font addressed by glyph id, with an explicit /CIDToGIDMap and a /ToUnicode CMap whose entries may span several codepoints — so a conjunct copies back as its full sequence.
  8. Wrap each line in a /Span <</ActualText …>> BDC … EMC marked-content span. This is what makes copy/paste survive Bengali's glyph reordering: কি draws ি first, so no per-glyph mapping alone can express logical order.

Line breaking happens on syllable boundaries, so a line never breaks inside a conjunct or between a vowel sign and its consonant.


🧪 Verification #

Every claim below is produced by code in this repository. The full write-ups live there rather than in the published package — see the verification report for the complete run, and the 1.0.6 teardown for how the old pipeline worked and why it broke.

Shaping is diffed glyph-by-glyph and position-by-position against HarfBuzz (hb-shape) over a 253-case corpus:

font exact match
Kalpurush (bundled) 234 / 234
Noto Sans Bengali 234 / 234
Noto Serif Bengali 234 / 234

Round-trip — every corpus case is rendered to a real PDF and extracted with pdftotext: 249 / 251 identical to the source. The two exceptions are one artefact: a lone ZWJ renders as nothing, so the line-by-line comparison shifts.

Structure — automated tests parse the generated PDF and assert it is a Type0 CID font, that every /ToUnicode entry is real Unicode with no U+FFFD or NUL, and that each line's /ActualText decodes back to its source string.

flutter test    # 26 tests

⬆️ Upgrading from 1.0.x #

Three things to know:

  1. Nothing to change. Text(...), banglaStyle:, banglaFont: and the no-initialisation default all behave as before. No API was removed.
  2. The typeface does not change. The bundled font is still Kalpurush, now its Unicode build instead of the 8-bit Bijoy one. Documents look the same; what changes is that the Bangla in them is shaped correctly.
  3. Custom Bijoy fonts keep working. A legacy 8-bit font passed to banglaFont: is detected and routed to the old pipeline automatically. To force the old behaviour everywhere, call BanglaPdf.configure(shapingMode: BanglaShapingMode.legacy) once at startup.

⚠️ Known limitations #

Stated plainly rather than glossed over.

  • PDF text extraction is not implemented. There is no package:bangla_pdf/extract.dart yet — no Bijoy→Unicode reverse mapping and no scanned-PDF handling.
  • Only three fonts are measured. Bundled Kalpurush, Noto Sans Bengali and Noto Serif Bengali all match HarfBuzz on every corpus case. SolaimanLipi, Siyam Rupali and others are untested. A font relying on GSUB lookup type 8 (reverse chaining) or GPOS type 3 (cursive attachment) would not shape — neither is implemented, because no tested Bengali font uses them.
  • Rendering is not pixel-diffed. Output was rasterised and compared against HarfBuzz reference renders by eye for a sample, not automatically for all 253 cases.
  • Copy/paste is verified with poppler only. Adobe Reader, macOS Preview, Chrome and Android viewers have not been tested, and /ActualText support does vary between them.
  • The full font is embedded in every PDF (121 KB); there is no per-document subsetter yet.
  • Emoji need a fallback font. The bundled font covers ASCII, the Bengali block and common punctuation. Emoji render as a placeholder box, and BulletList falls back from to · because Kalpurush has no bullet glyph.

🤝 Contributing #

Bug reports, corpus cases and pull requests are all welcome — a failing string is especially useful. Add it to test/corpus/bangla_cases.json with an id, category and notes, and the differential harness will pick it up.

flutter test                                  # unit and PDF-structure tests
dart run tool/dev/shape_dump.dart <font.ttf>  # diff against hb-shape
dart run tool/baseline/run_106_mapper.dart …  # the 1.0.6 baseline

hb-shape/hb-view (brew install harfbuzz) and pdftotext/pdftoppm (brew install poppler) are needed for the verification tooling, not for the package itself.


📄 License #

BSD 3-Clause — see LICENSE.

The bundled Kalpurush is by Md. Tanbin Islam Siyam (Avro Font Development Project, omicronlab.com) under the SIL Open Font License 1.0; its Latin glyphs are from Gentium. Font licences are in LICENSE-FONTS.txt.


💛 Credits #

Maintained by Zaman Sheikh · zaman6545@gmail.com

The legacy Bijoy pipeline kept for backward compatibility (BanglaShapingMode.legacy) descends from the ANSI transcoding approach in AR Rahman's bangla_pdf_fixer 2.x. It is retained only so 1.0.x users can reproduce their old output; the shaping in this package does not use it.

Thanks to every Bangla font creator whose work makes readable Bangla typography possible.

⭐ If this saved you a day of debugging, star the repo.

11
likes
0
points
660
downloads

Documentation

Documentation

Publisher

verified publisherzamansheikh.com

Weekly Downloads

Correct Bangla (Bengali) text in PDFs: real OpenType shaping for conjuncts, reph and vowel signs, with text that copies back out as clean Unicode.

Repository (GitHub)
View/report issues

Topics

#pdf #bangla #unicode #typography #text-shaping

License

unknown (license)

Dependencies

flutter, pdf, plugin_platform_interface

More

Packages that depend on bangla_pdf