EmojiText.Net 0.1.0

dotnet add package EmojiText.Net --version 0.1.0
                    
NuGet\Install-Package EmojiText.Net -Version 0.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="EmojiText.Net" Version="0.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="EmojiText.Net" Version="0.1.0" />
                    
Directory.Packages.props
<PackageReference Include="EmojiText.Net" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add EmojiText.Net --version 0.1.0
                    
#r "nuget: EmojiText.Net, 0.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package EmojiText.Net@0.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=EmojiText.Net&version=0.1.0
                    
Install as a Cake Addin
#tool nuget:?package=EmojiText.Net&version=0.1.0
                    
Install as a Cake Tool

EmojiText.NET

Emoji-aware text processing for .NET: detect, count, strip, and replace emoji as the user-perceived units they are, not as raw code points. Zero external dependencies.

Emoji are not one char, and often not even one Unicode scalar value. A family is five code points joined by zero-width joiners. A flag is a pair of regional-indicator letters. A thumbs-up with a skin tone is a base emoji plus a modifier. Naive code that iterates char by char, or even scalar by scalar, sees fragments where a human sees one glyph, and any naive "strip the emoji" regex either misses these multi-codepoint sequences or shreds them into partial matches. System.Globalization does not expose an RGI emoji property either, so anything built on it inherits whatever the OS happens to know. EmojiText.NET ships its own pinned, embedded table of RGI emoji sequences derived directly from the official Unicode data files, so detection is exact and does not drift with the OS or the .NET runtime it happens to run on.

Install

dotnet add package EmojiText.Net

Usage

Detect and count

using EmojiText;

Emoji.HasEmoji("Nice work! \U0001F44D\U0001F3FD");  // true
Emoji.Count("Team \U0001F468‍\U0001F469‍\U0001F467‍\U0001F466 outing \U0001F600"); // 2

The family emoji above is five code points joined by zero-width joiners (‍), and the skin-toned thumbs-up is two code points (base + modifier). Both count as exactly one emoji each, because that is what a person sees.

Strip emoji from user input before storage or logging

using EmojiText;

string comment = "Loved it \U0001F600\U0001F389 will buy again!";
Emoji.Strip(comment);
// "Loved it  will buy again!"   (naive removal leaves the double space)

Emoji.Strip(comment, collapseWhitespace: true);
// "Loved it will buy again!"

Enumerate matches for highlighting or redaction, or replace per match

using EmojiText;

foreach (var match in Emoji.EnumerateMatches("Rate: \U0001F44D\U0001F3FD price: 5️⃣"))
{
    Console.WriteLine($"{match.Value} at {match.Index}, {match.Length} UTF-16 chars");
}

// Replace every emoji with a shortcode instead of deleting it
Emoji.ReplaceEach("So good \U0001F600", match => $":u{char.ConvertToUtf32(match.Value, 0):X4}:");

API

A single static class, Emoji:

Member Purpose
HasEmoji(string?) Whether the text contains any RGI emoji
Count(string?) Number of emoji, each multi-codepoint sequence counted once
EnumerateMatches(string?) Lazily yields EmojiMatch { Value, Index, Length } for every match, left to right, indices and lengths in UTF-16 chars
Strip(string?, collapseWhitespace: false) Removes all emoji; optionally collapses whitespace that touched a removed emoji, trimming it away entirely at the start or end of the result. Whitespace elsewhere in the string is never touched
Replace(string?, string?) Replaces every emoji with a fixed string
ReplaceEach(string?, Func<EmojiMatch, string>) Replaces every emoji with a string computed from that match
UnicodeEmojiVersion The pinned Unicode emoji specification version the embedded data was built from

All methods accept null and treat it the same as an empty string; no exceptions for the common case of an optional, absent field.

What counts as an emoji here

Matching is scoped precisely to the RGI_Emoji property as defined by UTS #51: the union of the Basic_Emoji, Emoji_Keycap_Sequence, RGI_Emoji_Flag_Sequence, RGI_Emoji_Tag_Sequence, and RGI_Emoji_Modifier_Sequence properties from emoji-sequences.txt, plus RGI_Emoji_ZWJ_Sequence from emoji-zwj-sequences.txt. Both files are published by the Unicode Consortium and this library embeds their fully-qualified sequences directly (Unicode emoji version 16.0, exposed as Emoji.UnicodeEmojiVersion).

Four consequences worth knowing:

  • "Unqualified" text-presentation symbols are excluded by design. A bare ☺ (U+263A, no variation selector) is "unqualified" in Unicode's own test data and is not matched; ☺️ (U+263A U+FE0F) is fully-qualified and is matched. This is not an oversight, it is the RGI scope as Unicode defines it, and matches what modern keyboards and platforms actually emit.
  • Regional-indicator letters only match in the specific pairs Unicode recognizes as real or historical flags. A lone regional indicator, or a pair that spells no assigned flag, is left alone rather than guessed at. Pairing itself always follows UAX #29's strict left-to-right grouping (an odd one out never borrows its neighbor's partner), so a run like three regional indicators where only the first and second, or only the second and third, would spell a real flag is matched exactly the way a grapheme-cluster-aware renderer displays it, not by scanning ahead for whichever pairing happens to be assigned.
  • Skin-tone and hair-style modifiers (U+1F3FB-U+1F3FF, U+1F9B0-U+1F9B3) are themselves RGI Basic_Emoji, so a bare modifier with no base emoji in front of it still counts as one match. Applied after a base that supports it (e.g. 👍🏽), the pair is one match; applied after a base that Unicode does not list as modifiable (e.g. 😀🏽), it is reported as two separate matches, because that pairing simply is not an RGI sequence. This is the RGI table's word on it, not this library's guess, and it is a deliberate difference from grapheme-cluster segmentation, which would glue the two together as one cluster regardless of whether Unicode assigned that combination meaning.
  • Strip(text, collapseWhitespace: true) only touches whitespace that bordered a removed emoji. Two spaces left behind where an emoji used to sit collapse to one, and collapse away entirely if that leaves them at the very start or end of the result; whitespace anywhere else in the string, with no emoji ever near it, is left exactly as it was.

Why this exists

.NET has no built-in RGI_Emoji Unicode property, and the popular emoji npm/Python packages have no direct, zero-dependency .NET equivalent that ships pinned, versioned Unicode data rather than reaching into OS globalization tables. Regex-based approaches built on a handwritten emoji range are what most teams reach for instead, and they reliably fail on exactly the cases that matter: ZWJ families, flags, and skin-tone modifiers get split into several fake "emoji," while newly assigned single code points are silently missed until someone remembers to update the pattern. EmojiText.NET replaces the regex with an exact match against the real Unicode data.

Dependencies and AOT

Zero runtime NuGet dependencies. The library does no reflection over user types and performs no dynamic code generation; it reads its own embedded resource once via Assembly.GetManifestResourceStream and builds an in-memory trie. This is compatible with trimming and Native AOT in ordinary use. It has not been independently verified against the full Native AOT compatibility analyzer, so treat that as an expectation grounded in the implementation rather than a certified guarantee, and verify in your own AOT publish if that matters for your deployment.

License

MIT. See LICENSE.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net8.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.1.0 134 8/12/2026