Overview
Krutidev is a font-level encoding, not a character encoding. It stores Hindi text as ASCII characters that only render as Devanagari when the Krutidev font is active. Converting Krutidev to Unicode means replacing each ASCII character or character sequence with the corresponding Unicode Devanagari code point.
The conversion is a multi-phase process: character mapping, nukta normalization, matra repositioning, and reph handling. Each phase must execute in the correct order. Skipping or reordering phases produces incorrect output for any text containing chhoti i matra (ि) or reph (र्).
Phase 1: Character Mapping
The core of the conversion is a pair of parallel arrays: one containing Krutidev patterns (194 entries), the other containing the corresponding Unicode patterns. The converter iterates through the arrays and replaces every occurrence of each Krutidev pattern with its Unicode equivalent.
Array ordering is critical. The arrays are sorted so that longer patterns appear before shorter ones. Without this, "Fk" (थ) would never be matched because "F" (थ्) appears earlier and consumes the "F" before the algorithm sees "Fk". Our arrays place multi-character entries like "Fk", "[k", "?k", "Hk", ".k", "/k", "vks", "vkS", "vk" before their single-character substrings.
The replacement loop for each pattern continues until no more instances exist in the text. This is necessary because some patterns can appear adjacent or overlapping in complex text. A single-pass replacement would miss instances created by prior replacements in the same iteration.
Phase 2: Nukta Normalization (Unicode to Krutidev only)
Unicode represents nukta characters (borrowed Urdu/Arabic sounds) in two ways: as a precomposed character (e.g., क़ = U+0958) or as a base character plus a combining nukta (e.g., क + ़ = U+0915 + U+093C). Both represent the same sound and should produce the same Krutidev output.
Before running the main mapping, we normalize all decomposed nukta sequences to their precomposed equivalents: क+़ becomes क़, ख+़ becomes ख़, ग+़ becomes ग़, ज+़ becomes ज़, ड+़ becomes ड़, ढ+़ becomes ढ़, फ+़ becomes फ़, य+़ becomes य़. This ensures consistent matching in the mapping phase regardless of how the input was encoded.
Phase 3: Chhoti I Matra Repositioning
The chhoti i matra (ि, U+093F) is the most complex aspect of Krutidev conversion. In Unicode, matras come after their base consonant: कि = क + ि. In Krutidev, the chhoti i matra is typed before the consonant: "f" (ि) + "d" (क) = "fd" which renders as "कि" in the font.
Krutidev to Unicode: After the main mapping, any remaining "f" characters are chhoti i matras that need repositioning. The algorithm finds each "f", takes the next character (the consonant), and swaps them: "fX" becomes "Xि". For consonant clusters with halant (e.g., "f" before a halant+consonant sequence), the "f" must move past the entire cluster, not just the adjacent character.
Unicode to Krutidev: The reverse operation moves ि from after the consonant to before it (as "f"). When the consonant has a preceding halant+consonant (a conjunct), the "f" must move to before the entire cluster, not just the immediate consonant.
Phase 4: Reph Handling
Reph is the half-र that sits above the following consonant cluster: "र्म" (rma) shows र above म. In Unicode, reph is represented as र + ् (ra + halant) before the base consonant. In Krutidev, reph is represented as "Z" placed after the base consonant and its matras.
Krutidev to Unicode: After all other processing, any remaining "Z" characters are reph markers. The algorithm finds each "Z", looks backward past any matras to find the base consonant, and moves the reph to before the consonant as "र्". The matra list used for backward scanning includes: ा ि ी ु ू ृ े ै ो ौ ं ँ ः ॅ.
Unicode to Krutidev: The reverse operation finds each "र्" (ra + halant), takes the following consonant and its matras, and places "Z" after the entire group.
Phase 5: Additional Special Cases
Several Krutidev characters require individual handling outside the main mapping:
- Character Ç (0xC7): Maps to "fa" (chhoti i matra + anusvara). Requires the same repositioning as regular chhoti i matra but with an additional anusvara after the consonant.
- Character É (0xC9): Maps to "र्fa" (reph + chhoti i matra + anusvara). Combines both reph and matra repositioning in a single character.
- Character Ê (0xCA): Maps to "ीZ" (long i matra + reph). The reph repositioning phase handles the Z component.
- Character ± (0xB1): Maps to "Zं" (reph + anusvara). The anusvara stays in place while Z undergoes reph repositioning.
- Character Æ (0xC6): Maps to "र्f" (reph + chhoti i matra). Both repositioning phases apply.
Known Limitations
No Krutidev converter is 100% accurate for all possible inputs. These are the known edge cases where our converter (and all existing converters) may produce imperfect output:
- Rare conjuncts: Conjuncts not in the mapping table (e.g., some Vedic Sanskrit combinations) pass through unconverted. The mapping covers the 40+ most common Hindi conjuncts.
- Mixed-language text: English words embedded in Krutidev text may be partially converted if they happen to contain character sequences that match Krutidev patterns. For mixed-language documents, we recommend reviewing the output to check that English words converted correctly.
- Custom Krutidev variants: Some organizations have modified Krutidev fonts with custom character mappings. Our converter uses the standard Kruti Dev 010 mapping. Non-standard variants may produce incorrect output for their custom characters.
- Nested reph+matra combinations: Extremely complex constructions with reph, chhoti i matra, and anusvara stacked on the same consonant cluster may occasionally produce incorrect character ordering. This affects fewer than 0.1% of real-world texts.
Comparison with Other Implementations
Most online Krutidev converters use the same fundamental algorithm: mapping arrays + special-case handling. The differences are in implementation quality:
- Chunking: Some converters split input at 7,000 characters and process each chunk separately. This can break words that span chunk boundaries. Our implementation processes the full text without chunking.
- Speed: Several converters require a round-trip to a server, adding latency. Our converter produces output instantly with no wait time.
- Deprecated APIs: Some competitors use document.execCommand("copy") for clipboard access, which is deprecated and fails in modern browsers. We use the modern navigator.clipboard API with a fallback.