tecnickcom/tc-lib-unicode
tc-lib-unicode is a PHP library for working with Unicode text, offering character data, normalization forms, case mapping, and grapheme-aware string utilities. It helps PHP applications handle multilingual input correctly through a clean object-oriented API.
tecnickcom/tc-lib-unicode is a low-level, dependency-free PHP utility library from the Tecnick/TCPDF ecosystem. It is not an application framework and exposes no service provider, facade, or config file — it is a set of namespaced classes you call statically. Treat it as the "Unicode plumbing" layer underneath tecnickcom/tc-lib-pdf and tecnickcom/tc-lib-barcode.
Install and locate the good stuff
composer require tecnickcom/tc-lib-unicode
Then immediately read the source, because the README is thin and there is no hosted API docs:
ls vendor/tecnickcom/tc-lib-unicode/src
# Expect something like: Convert.php, Bidi.php, Data/
grep -R "public static function" vendor/tecnickcom/tc-lib-unicode/src
Look first at:
src/Convert.php — the workhorse. This is where UTF-8 ↔ UTF-16BE/LE, UTF-32, and code-point conversion lives.src/Data/ — raw Unicode tables (bidirectional classes, mirroring pairs, combining marks). These are generated data arrays, not logic; skim them but do not edit them.tests/ — the fastest way to see the intended call signatures and edge-case expectations.First use case
The canonical reason to reach for this package is: you have a UTF-8 PHP string and something downstream wants UTF-16BE bytes (a PDF text string, a Windows API, a Java-style consumer). Rather than pulling in mbstring/iconv behaviour that varies by build, you get deterministic, pure-PHP output:
use Com\Tecnick\Unicode\Convert;
$utf8 = 'Grüße — 日本語';
$utf16be = Convert::utf8ToUtf16BE($utf8); // verify exact method name in src/Convert.php
Verify the exact method names against src/Convert.php before writing production code — the class has accumulated aliases over time.
Because the API is a bag of static methods, direct calls become untraceable after a few months. Put a thin adapter in your app so you can swap implementations, add logging, or fall back to mb_convert_encoding:
namespace App\Support;
use Com\Tecnick\Unicode\Convert;
final class Unicode
{
public static function toUtf16Be(string $utf8): string
{
return Convert::utf8ToUtf16BE($utf8);
}
public static function codePoints(string $utf8): array
{
return Convert::utf8ToCodepoints($utf8);
}
}
This also gives you one place to fix BOM policy (see Gotchas).
If you are integrating with TCPDF or tc-lib-pdf, the library is already a transitive dependency — do not call it directly for PDF text. Let TCPDF handle string encoding and only use this package when you are writing custom font or content streams:
// Building a custom CID font ToUnicode CMap or a text object manually
$hex = bin2hex(Unicode::toUtf16Be($userText));
$content = "<{$hex}> Tj";
Batch your conversions. Calling into the converter per glyph is the classic performance mistake:
// Good: one call per run of text
$runs = preg_split('/(?<=[\x{0600}-\x{06FF}])/u', $text);
foreach ($runs as $run) { $stream .= encodeRun($run); }
src/Bidi.php exists specifically so the PDF stack can reorder RTL runs. If you generate PDFs or any fixed-layout output containing RTL text, use it to produce visual-order runs before encoding:
// Pseudocode — confirm class/method names in src/Bidi.php
$runs = (new \Com\Tecnick\Unicode\Bidi($text))->getRuns();
foreach ($runs as $run) {
$encoded[] = Unicode::toUtf16Be($run['text']); // run already reordered
}
Key workflow point: bidi resolution is a layout concern, conversion is an encoding concern. Keep them in that order — never reorder after converting to UTF-16.
src/Data/ is genuinely useful on its own, independent of the converters. If you need to know whether a character is a combining mark, a mirroring bracket, or an RTL class, reference the tables instead of maintaining your own:
use Com\Tecnick\Unicode\Data\Bidi; // confirm namespace
$class = Bidi::$bidiClass[$codepoint] ?? 'L';
This is the cheapest way to stay Unicode-consistent with the rest of the Tecnick stack.
Com\Tecnick\Unicode\. No bootstrap file, no init call — composer require is the whole setup.composer.lock plus a deliberate upgrade step, not ^ floating.This is not ICU. The package gives you encoding conversion and bidi tables. It is not a collation engine, not a locale-aware formatter, and — depending on the version — not a full Unicode Normalization (NFC/NFD) implementation. If you need intl-grade behaviour, use ext-intl and keep this package for the byte-level plumbing.
BOM and byte order will bite you. UTF-16BE vs UTF-16LE vs "UTF-16 with BOM" are three different things, and PDF text strings require the BOM in specific contexts. Decide once in your adapter whether you emit FEFF, and test with a downstream consumer rather than eyeballing hex.
Surrogate pairs and astral characters. Emoji and anything above U+FFFF become two UTF-16 code units. Any code that assumes "one code point = one character = one column" will break. Round-trip emoji through your pipeline in tests.
It is pure PHP, so it is slower than mbstring/iconv. For high-volume conversion (thousands of strings per request), measure. Caching converted strings keyed by a hash of the input is often the right fix, since PDF/barcode output tends to repeat.
The data tables are large. Loading src/Data/* pulls in big PHP arrays; make sure OPcache is enabled in production or you will pay the parse cost on every request.
API surface drift. The method names (utf8ToUtf16BE vs utf8ToUtf16be vs an alias) have varied across releases. Do not trust blog posts — grep "function" vendor/tecnickcom/tc-lib-unicode/src/Convert.php and write your adapter against what is actually installed.
NOASSERTION licence. The package metadata does not declare a clean SPDX identifier, though the Tecnick projects are generally LGPL-3.0. Before shipping in a commercial product, confirm the licence in the repo's LICENSE file and get sign-off. This is the single biggest blocker for adopting it standalone rather than as a transitive dependency.
Debugging technique. When output looks wrong, hex-dump at both ends of your pipeline (bin2hex() before and after) and diff against a known-good reference such as iconv. The bug is almost always in your BOM policy or in reordering after encoding, not in the library.
Extension points are minimal by design. There are no interfaces to implement and no DI hooks. The supported extension pattern is composition: wrap the static classes, subclass only if a method is non-static and you need to override it, and otherwise leave src/Data/ untouched. If you find yourself needing to patch the data tables, you almost certainly want a newer release instead.
When not to use it. If you are just consuming tc-lib-pdf or tc-lib-barcode, you never need to require this package explicitly — it will be pulled in. Reach for it directly only when you are producing Unicode-encoded bytes yourself.
How can I help you explore Laravel packages today?