bangla_pdf 1.1.0
bangla_pdf: ^1.1.0 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.
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.

Quick start #
dependencies:
bangla_pdf: ^1.1.0
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 — see Verification.
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('এবং সাধারণ'),
])
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 #
- Normalise — NFC and NFD are reconciled, two-part vowels (
োৌ) are split the way font rules expect, and nukta pairs are composed. - Segment the text into Indic syllables.
- Find the base consonant by asking the font which consonants it gives below-base or post-base forms to.
- Reorder — pre-base matras move in front of the cluster; reph moves after the base and any below-base form.
- Apply GSUB in the OpenType Indic order —
nukt akhn rphf blwf half pstf vatu cjct, thenpres abvs blws psts haln, thencalt clig rclt rlig— each under the right per-glyph mask. - Apply GPOS —
dist abvm blwm mark mkmk kern, including mark-to-base, mark-to-ligature and mark-to-mark attachment. - Emit PDF text as a
Type0/Identity-HCID font addressed by glyph id, with an explicit/CIDToGIDMapand a/ToUnicodeCMap whose entries may span several codepoints — so a conjunct copies back as its full sequence. - Wrap each line in a
/Span <</ActualText …>> BDC … EMCmarked-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:
- Nothing to change.
Text(...),banglaStyle:,banglaFont:and the no-initialisation default all behave as before. No API was removed. - 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.
- 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, callBanglaPdf.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.dartyet — 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
/ActualTextsupport 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
BulletListfalls 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.