bangla_pdf 1.6.0
bangla_pdf: ^1.6.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
Bangla PDFs that actually look right.
Change one import. Everything else stays exactly as it is.
Write Bangla, get Bangla.
pw.Text('আমার সোনার বাংলা, আমি তোমায় ভালোবাসি।')
That is the package:pdf widget you already use, from an import that shapes
Bangla properly. Conjuncts join, ি ে ৈ land on the
correct side of their consonant, reph sits where it belongs — and the text you
copy out of the PDF is the text you put in.

📄 Made with it #
Real output from the widgets below — no mockups. Every conjunct, matra and reph is placed by the shaper, and every word stays selectable and searchable.
![]() |
![]() |
| ষাণ্মাসিক প্রতিবেদন Metric cards and a bar chart — Bangla axis labels and legend |
চালান A styled table, Bengali digits and the taka sign |
![]() |
![]() |
| বিজ্ঞপ্তি Headings, a justified paragraph and a bulleted list |
প্রতিবেদন Bangla and English mixed, in one pass |
All four are generated by
tool/dev/make_showcase.dart.
The chart page is written against the package:bangla_pdf/widgets.dart
drop-in; the other three use the package's own widgets.
🚀 Get started #
dependencies:
bangla_pdf: ^1.6.0
Now change one import:
- import 'package:pdf/widgets.dart' as pw;
+ import 'package:bangla_pdf/widgets.dart' as pw;
That is the entire migration. Your existing code is untouched:
import 'package:bangla_pdf/widgets.dart' as pw;
final pdf = pw.Document();
pdf.addPage(
pw.Page(
build: (context) => pw.Text('আমার সোনার বাংলা'),
),
);
final bytes = await pdf.save();
No font to bundle, nothing to initialise, no new widget names to learn. A Bangla font ships with the package and is used automatically.
🧩 Nothing new to learn #
You keep writing package:pdf code exactly as you always have:
pw.Header(level: 1, text: 'গণপ্রজাতন্ত্রী বাংলাদেশ সরকার')
pw.Paragraph(text: 'একটি অনুচ্ছেদ যেখানে ক্ষ্ম ও কর্ম দুটোই ঠিক আসে।')
pw.Bullet(text: 'প্রথম আইটেম')
pw.TableHelper.fromTextArray(
headers: ['পণ্য', 'পরিমাণ', 'মূল্য'],
data: [
['কফি', '২', '৳২০'],
],
)
Same class names, same parameters, same everything. The import quietly swaps in Bangla-aware versions of the widgets that draw text:
Text·RichText·TextSpan·Header·Paragraph·Bullet·TableHelper·Watermark·TableOfContent·ChartLegend·FixedAxis·TextField·ChoiceField
That is every widget in package:pdf that puts text on a page — so Bangla
also comes out right in places you never think about: chart axis labels and
legends, watermarks, tables of contents, and the values inside form fields.
Every other widget — pw.Page, pw.Column, pw.Container, pw.MultiPage,
images, barcodes — is literally the same class from package:pdf. This package
does not wrap or re-implement them.
Bangla, English, digits and ৳ mix freely in one string, with nothing to split
up by hand:
pw.Text('Invoice #1042 — মোট ৳১২,৫০০.০০ — তারিখ ০১/০৯/২০২৬')
And a string with no Bangla in it never touches this package — it goes
straight to package:pdf, so your English pages render exactly as they do
today.
Long documents work the way you would expect: inside a pw.MultiPage, a Bangla
paragraph marked overflow: TextOverflow.span breaks across pages on a line
boundary, exactly as a Latin one does.
Prefer explicitly Bangla-named widgets? Those still exist.
If you would rather see at a glance which widgets are Bangla-aware, import the main library instead and use its own widgets. This is the original 1.0 API and it is not going anywhere:
import 'package:bangla_pdf/bangla_pdf.dart';
import 'package:pdf/widgets.dart' as pw;
Text('বাংলা টেক্সট')
Header('বাংলা শিরোনাম', level: 1)
Paragraph('একটি অনুচ্ছেদ।')
BulletList(items: ['প্রথম আইটেম', 'Second item'])
Table(data: [['পণ্য', 'মূল্য'], ['কফি', '৳২০']])
RichText(spans: [TextSpan('বাংলা বোল্ড ', fontWeight: pw.FontWeight.bold)])
These differ slightly from pw on purpose: Header and Paragraph take their
text positionally, Table wraps TableHelper.fromTextArray, and BulletList
takes a whole list. Use package:bangla_pdf/widgets.dart when you want the
pw shapes instead — the two mix freely in one file.
📥 Reading Bangla back out of a PDF #
Point it at a PDF and get the text:
import 'package:bangla_pdf/extract.dart';
final result = BanglaPdfExtractor.extract(bytes);
print(result.text);
print(result.encodingDetected); // unicode | bijoy | mixed | none
It also rescues Bijoy documents — the government and newspaper PDFs where
copying text gives you Avgvi ‡mvbvi evsjv instead of আমার সোনার বাংলা. Those
are detected and converted back for you.
Scanned pages report BanglaTextEncoding.none instead of guessing, and you can
plug in whichever OCR you already use:
BanglaPdfExtractor.extract(
bytes,
ocrHook: (page) => runTesseract(page.number, language: 'ben'),
);
It is a separate library, so generating PDFs costs nothing if you never import it.
🔤 Want a different font? #
Name it in an ordinary TextStyle, like any other font:
import 'package:flutter/services.dart' show rootBundle;
final solaiman = pw.Font.ttf(
await rootBundle.load('assets/fonts/SolaimanLipi.ttf'),
);
pw.Text('বাংলা', style: pw.TextStyle(font: solaiman));
To use it everywhere, set it once at startup:
BanglaPdf.configure(defaultFont: solaiman);
SolaimanLipi, Siyam Rupali, Noto Sans Bengali and Noto Serif Bengali are all tested and match HarfBuzz exactly.
Characters your Bangla font doesn't have #
No Bangla typeface covers accented Latin, arrows, symbols or emoji — the bundled Kalpurush has 206 glyphs, and even Noto Sans Bengali has 444. Anything outside that simply doesn't draw.
Give it somewhere to fall back to, exactly as you would in package:pdf:
// once, for the whole document
BanglaPdf.configure(fallbackFonts: [notoSans, notoEmoji]);
// or for one run
pw.Text('বাংলা café ± 50°C', style: pw.TextStyle(fontFallback: [notoSans]));
Each character is drawn by the first font in the chain that has it, so
বাংলা café ± 50°C comes out whole. Bengali is never affected — it is always
drawn by your Bangla font, and a fallback boundary never falls inside a
conjunct.
Already using a Bijoy (8-bit) font? Keep passing it. It is recognised by
what it actually contains — Bangla glyphs reached through Latin-1 byte values,
with no Bengali in its cmap — and your text is transcoded and drawn with
your font, exactly as in 1.0.x. Nothing silently substitutes the bundled one.
⬆️ Coming from bangla_pdf 1.0? #
Nothing to change. Text(...), banglaStyle:, banglaFont: and the
no-setup default all work as before, and the bundled typeface is still
Kalpurush — your documents look the same, the Bangla in them is just shaped
correctly now. The package:bangla_pdf/widgets.dart entry point above is
purely additive; you can adopt it file by file, or never.
What changed, in detail
- Reph no longer crashes.
কর্ম,ধর্ম,বর্ষ,পূর্ব,শর্তthrew an uncaughtRangeErrorin 1.0.6 that abortedpdf.save()outright. 21 of 253 corpus cases crashed; none do now. - Conjuncts join.
ক্ষ্ম,ঙ্ক্ষ,ত্ত্ব,চ্ছ্ব,ম্ভ্র,স্ত্র্যused to render as a base plus a stray hasanta. - Digits and currency survive.
০–৯silently became0–9, and৳became$. - Copy, search and screen readers work. The PDF now holds real Unicode instead of Bijoy ANSI.
ৰৱ॥render instead of showing a missing-glyph box, andর্যis now distinguished fromর্য.- One font draws the whole string, so
styleandbanglaStyleno longer give Bangla and Latin different looks inside one widget;banglaStylewins when both are set. Use two widgets for two looks. AutoTextandRichTextItemare deprecated.Textis identical toAutoText— splitting a string by script stopped being necessary once the Bangla font covered Latin and digits too — andRichTextItemwas never used by anything. Both still work and are removed in 2.0.0.- To get byte-identical 1.0.x output back, call
BanglaPdf.configure(shapingMode: BanglaShapingMode.legacy)once at startup.
✅ How well does it work? #
Shaping is compared glyph by glyph against HarfBuzz — the engine behind Chrome, Android and LibreOffice — over a 253-case corpus:
| font | exact match |
|---|---|
| Kalpurush (bundled) | 234 / 234 |
| SolaimanLipi | 234 / 234 |
| Siyam Rupali | 234 / 234 |
| Noto Sans Bengali | 234 / 234 |
| Noto Serif Bengali | 234 / 234 |
The text survives the round trip too: 249 of 251 cases come back out of a
generated PDF identical to what went in, checked with pdftotext. Extraction
recovers 8 of 8 fixture documents exactly and correctly reports both scanned
ones as having no text layer.
Signature parity is checked too: a script diffs all 14 replacement
constructors against their package:pdf counterparts, and all 156
parameters match.
All of it runs on every commit — flutter test is 73 tests.
How the shaping actually works
Bangla is shaped with the font's own OpenType GSUB/GPOS tables, in pure
Dart — no FFI and no C toolchain, which is why this also works on Flutter web.
- 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. Both generations of the spec are handled:bng2fonts write their rules as virama + consonant,bengfonts the other way round. - Apply GPOS —
dist abvm blwm mark mkmk kern, including mark-to-base, mark-to-ligature and mark-to-mark attachment. - Emit 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.
Lines break on syllable boundaries, so a line never splits inside a conjunct or between a vowel sign and its consonant.
Full write-ups live in the repository: the verification report and the 1.0.6 teardown.
⚠️ Known limitations #
- Extraction cannot rescue every third-party PDF. A document with no
/ToUnicodeand no/ActualTextcannot be recovered — that needs reversing the shaping from glyph ids back to characters, which is not implemented. - Encrypted PDFs are not decrypted.
ExtractionResult.isEncryptedsays so rather than returning nonsense. - Emoji and symbols need a fallback font. No Bangla typeface contains them — not Kalpurush, not Noto Sans Bengali, not SolaimanLipi. Supply one (see above) and they render.
- The full font is embedded in every PDF (121 KB) — there is no per-document subsetter yet.
- Five fonts are measured. Others should work but are untested. A font relying on GSUB lookup type 8 or GPOS type 3 would not shape; no Bengali font tested uses either.
- Right-to-left text is not reordered.
textDirectiondecides which edgeTextAlign.startresolves to, but a right-to-left script mixed into a string is drawn in logical order — that needs a bidi pass, which is not implemented. Bengali itself is left-to-right, so this only matters for mixed Arabic or Hebrew. - Form fields shape only their appearance.
pw.TextFieldandpw.ChoiceFielddraw a shaped value, but once a reader lets someone edit the field it re-renders from the form font. No PDF producer controls that. - Rendering is not pixel-diffed, and copy/paste is verified with poppler only — not Adobe Reader, Preview or Chrome.
- Extraction fixtures are generated, not collected — shaped like real notices, invoices and newspaper pages, but not downloaded from a government website.
🤝 Contributing #
Found Bangla that renders wrong? That is the most useful bug report there is.
Add the string to test/corpus/bangla_cases.json with an id,
category and notes, and the differential harness picks it up.
flutter test # unit and PDF-structure tests
dart run tool/dev/shape_dump.dart <font.ttf> # diff against hb-shape
hb-shape (brew install harfbuzz) and pdftotext (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.



