Iconvex Bibliographic

iconvex_bibliographic is the source-qualified library, cataloguing, and TeX extension for Iconvex. It is pure Elixir: there is no NIF, port process, platform iconv, or runtime dependency on iconvex_specs.

Add the package and its Core dependency:

def deps do
[
{:iconvex, "~> 0.1.1"},
{:iconvex_bibliographic, "~> 0.1"}
]
end

Starting the OTP application atomically registers every byte codec. Stopping it atomically removes only the routes owned by this package.

The collection

FamilyCanonical encodingWhat makes it interesting
MARCMARC-8Stateful ISO 2022 designations, EACC 24-bit characters, ANSEL combining marks, and spanning half marks
MARCANSELStrict ANSI/NISO Z39.47 ASCII + Extended Latin environment
CorkTEX-T1-EC-GLYPHThe EC glyph interpretation, including ligature scalars and exact SS sequence semantics
CorkTEX-T1-CMAP-1.0JCTAN CMap extraction with deterministic longest-match ffi/ffl/ff/fi/fl encoding
OT1TEX-OT1-CMAP-1.0JNormal CTAN OT1 extraction; byte 20 is deliberately undefined
OT1TEX-OT1TT-CMAP-1.0JTypewriter OT1, where byte 20 is U+2423 OPEN BOX
TeX mathTEX-LIVE-OML-CMMI10-TOUNICODE-2026Exact 7-bit cmmi10 ToUnicode semantics, with packed MSB/LSB transports
TeX mathTEX-LIVE-OMS-CMSY10-TOUNICODE-2026Exact 7-bit cmsy10 ToUnicode semantics, with packed MSB/LSB transports
Glyph vectorsCTAN-LY1-TEXNANSI-1.1-AGL-4036A9CAA pinned TeX'n'ANSI 1.1 vector resolved through a pinned Adobe Glyph List revision
Glyph vectorsADOBE-POSTSCRIPT-3-ISOLATIN1-AGL-4036A9CAPostScript LanguageLevel 3 ISOLatin1Encoding—specifically not ISO-8859-1

Iconvex.Bibliographic.registrations/0, codecs/0, encodings/0, and packed_profiles/0 expose the complete manifest-ordered surface.

MARC-8: combining marks and EACC

MARC-8 stores an ANSEL combining mark before its base character. Iconvex gives you normal Unicode order:

iex> Iconvex.convert(<<0xE2, ?e>>, "MARC-8", "UTF-8")
{:ok, "e\u0301"}
iex> Iconvex.convert("e\u0301", "UTF-8", "MARC8")
{:ok, <<0xE2, ?e>>}

The same codec handles custom and ISO 2022 designations plus three-byte EACC:

iex> Iconvex.Bibliographic.MARC8.decode(<<0x1B, ?$, ?1, 0x21, 0x30, 0x21>>)
{:ok, [0x4E00]}
iex> Iconvex.Bibliographic.MARC8.decode(<<0xEB, ?a, 0xEC, ?b>>)
{:ok, [?a, 0x0361, ?b]}

TeX ligatures with exact inverse semantics

The CMap profiles encode the longest source-defined sequence first:

iex> Iconvex.convert("ffifflfffifl", "UTF-8", "TEX-OT1-CMAP-1.0J")
{:ok, <<0x0E, 0x0F, 0x0B, 0x0C, 0x0D>>}
iex> Iconvex.convert("ffiAfflASS", "UTF-8", "TEX-T1-CMAP-1.0J")
{:ok, <<0x1E, ?A, 0x1F, ?A, 0xDF>>}

These are Unicode-extraction profiles, not claims that every historical font with the same broad label has identical semantics.

Seven-bit math, actually packed

The ordinary OML and OMS codecs use one logical 7-bit unit per byte, which is convenient for Iconvex.convert/4. The packed facade also emits true contiguous units:

alias Iconvex.Bibliographic.Packed
iex> Packed.encode_from_utf8("Γ∆", "OML")
{:ok, <<0::7, 1::7>>}
iex> {:ok, lsb} = Packed.encode_from_utf8("Γ∆", "OML-PACKED-LSB")
iex> lsb
%Iconvex.Packed.LSB{data: <<0x80, 0x00>>, bit_size: 14, unit_bits: 7, bit_order: :lsb}
iex> Packed.decode_to_utf8(lsb, "OML-PACKED-LSB")
{:ok, "Γ∆"}

The fully qualified named forms are TEX-LIVE-OML-CMMI10-TOUNICODE-2026-PACKED-MSB/-PACKED-LSB and the corresponding OMS names. Explicit and name-implied bit orders must agree.

Streams and recovery policies

Every codec implements the Iconvex strict, discard, substitution, direct UTF-8, and chunk callbacks. Stateful MARC-8 and longest-match TeX sequences remain correct across arbitrary stream boundaries:

chunks = [<<0x1B, ?$, ?1, 0x21>>, <<0x30, 0x21>>]
{:ok, stream} = Iconvex.stream(chunks, "MARC-8", "UTF-8")
IO.iodata_to_binary(Enum.to_list(stream))
# => "一"

Why the long names?

Many typographic “encodings” are really a vector + glyph list + extraction policy + source revision. Short aliases would silently merge distinct things. The canonical names retain the source boundary; only reviewed, unambiguous aliases are registered.

The package ships the exact runtime/source assets needed by those profiles, their SHA-256-bearing metadata, retained AGL/LPPL terms, and the 12-row SURFACE_MANIFEST.tsv. Tests exhaust all 256 bytes where applicable, all 128 OML/OMS units, inverse mappings, malformed input, every stream split, all 1,112,064 Unicode scalar values for both glyph-vector encoders, application restart ownership, and both packed orders.

See BENCHMARKS.md for reproducible native-path gates and NOTICE for upstream provenance and license boundaries.