# Unicode

## Hvad det er

Unicode er standarden, der giver hvert bogstav, hvert ciffer, hvert
tegnsætningstegn og hvert symbol i verdens skriftsystemer sit eget nummer.
Ifølge dem, der vedligeholder standarden, er Unicode den universelle
tegnkodning: bygget, så tekst på den moderne verdens sprog og i dens tekniske
fag kan udveksles, behandles og vises overalt, og udvidet til de klassiske og
historiske tekster i mange skriftsprog. Ét og samme repertoire rummer det danske
å, det ukrainske Ґ, eurotegnet og bogstaverne i skrifter, som ingen har skrevet
i århundreder, så et enkelt dokument eller en enkelt kolonne i en database kan
indeholde dem alle.

Formelt består en version af Unicode-standarden af kernespecifikationen,
kodeoversigterne, standardens bilag, Unicode Standard Annexes, og Unicode
Character Database, de maskinlæsbare filer, der beskriver hvert tegns
egenskaber. Unicode Technical Committee vedligeholder standarden løbende,
Unicode-konsortiet udgiver den, og version 18.0 blev annonceret
den 16. september 2026.

Det nummer, et tegn får, kaldes dets kodepunkt. Koderummet går fra 0 til 10FFFF
hexadecimalt, i alt 1.114.112 kodepunkter, og et kodepunkt skrives som U+
efterfulgt af mindst fire hexadecimale cifre: å er U+00E5, Ґ er U+0490, og
eurotegnet er U+20AC. Konsortiets stabilitetspolitik lover, at et tegn aldrig
flyttes eller fjernes, når det først er kodet, og at dets navn aldrig ændres, så
tekst, der kodes i dag, betyder det samme i alle senere versioner.

To skel bærer det meste af standarden. Det første går mellem tegn og glyf.
Unicode koder tegnet, det abstrakte bogstav, og overlader formen til fonten:
kernespecifikationen slår fast, at glyffernes udseende er fontleverandørernes
ansvar og ikke en del af standarden. Det samme kodepunkt kan se forskelligt ud i
to skrifttyper, og en skrifttype uden en form for det kan slet ikke tegne det.
Det andet skel går mellem kodepunktet og de bytes, der gemmer det. Unicode
definerer tre kodningsformer, UTF-8, UTF-16 og UTF-32, der skriver hvert
kodepunkt som en eller flere enheder på 8, 16 eller 32 bit, og specifikationen
kalder det en fælde at gøre det til et spørgsmål om »UTF-8 versus Unicode«: alle
tre er lige gyldige måder at implementere de samme tegn på.

## Hvorfor det betyder noget

Før Unicode havde hver skrift sine egne tegntabeller, og en byte betød
ingenting, før man vidste, hvilken tabel der var i brug. I den vesteuropæiske
tegntabel windows-1252 er byten E5 et å; i den kyrilliske windows-1251 er samme
byte bogstavet е. Ingen af dem kunne rumme den andens bogstaver: windows-1251
har Ґ, men hverken å, æ eller ø, og windows-1252 har det omvendt. Et produkt på
dansk og ukrainsk krævede to kodninger og et pålideligt overblik over, hvilken
tekst der brugte hvilken, og en sætning, der citerede begge sprog, kunne slet
ikke gemmes.

Unicode gjorde det bogholderi overflødigt. Alle de sprog, et produkt udgives på,
og alle de navne, en kunde skriver i en formular, passer ind i ét tegnsæt, og
det samme nummer betyder det samme tegn på alle systemer, der læser det. Det er
det, der lader en database, et API og en browser sende tekst imellem sig uden
først at blive enige om en tegntabel.

Standarden afgør også spørgsmål, som ellers ville blive afgjort forskelligt i
hvert eneste program. Den slår tegn sammen på tværs af sprog: kommaet er ét
tegn, uanset om det skiller tusinder ad, som på engelsk, eller decimaler, som på
dansk, så det er sproget og dets konventioner, ikke kodepunktet, der afgør
betydningen. Den slår ikke tegn sammen på tværs af skrifter: det latinske o, det
kyrilliske о og det græske ο ser ens ud i de fleste skrifttyper og er tre tegn,
fordi de hører til tre skrifter, og ældre kodninger allerede holdt dem adskilt.
Og den fastlægger, hvilke sekvenser der er ækvivalente. Å kan gemmes som ét
samlet tegn eller som A efterfulgt af en kombinerende ring, og standarden
definerer de to som kanonisk ækvivalente, så de skal vises og forstås ens, med
normaliseringsformer, der fører begge over i én aftalt stavemåde.

Den rækkevidde har en pris, og det er programmøren, der betaler den. Et tegn er
ikke en byte, ikke altid én enhed i en streng og ikke altid det, en læser tæller
som ét bogstav. Kode, der behandler de tre som det samme, klarer sine test med
engelske data og fejler på navne, adresser og beskeder fra netop de mennesker,
den blev skrevet til.

## Sådan ser det ud i praksis

JavaScript gør lagene synlige. En streng er en række UTF-16-kodeenheder, og
spreder man den ud i et array, deles den op i kodepunkter:

```js
const hex = (n) => n.toString(16).toUpperCase().padStart(4, "0");
for (const text of ["a", "å", "Ґ", "€", "𐌂"]) {
  const points = [...text].map((c) => "U+" + hex(c.codePointAt(0)));
  console.log(text, points.join(" "), text.length, [...text].length);
}
```

Kørt i Node.js 25 skriver koden hvert tegn, dets kodepunkt, dets længde i
UTF-16-enheder og dets længde i kodepunkter:

```text
a U+0061 1 1
å U+00E5 1 1
Ґ U+0490 1 1
€ U+20AC 1 1
𐌂 U+10302 2 1
```

Den sidste linje er et gammelitalisk bogstav. Dets kodepunkt ligger over U+FFFF,
så UTF-16 gemmer det som to enheder, et surrogatpar, og `length` melder 2 for
det, der er ét tegn.

Ækvivalens viser sig på samme måde. Her er tre strenge, der alle vises som Å:

```js
const forms = ["\u00C5", "A\u030A", "\u212B"];
console.log("form length equal NFC");
for (const [i, s] of forms.entries()) {
  console.log(i + 1, s.length, s === forms[0], s.normalize("NFC") === forms[0]);
}
```

```text
form length equal NFC
1 1 true true
2 2 false true
3 1 false true
```

Den første er det samlede bogstav, U+00C5. Den anden er et almindeligt A
efterfulgt af U+030A, den kombinerende ring over. Den tredje er U+212B,
ångstrømtegnet, som Unicode Character Database fører tilbage til U+00C5.
Sammenlignet, som de er, er de tre forskellige strenge; efter normalisering til
NFC er alle tre den samme. Intet på skærmen skiller dem ad, så en søgning, et
unikt indeks eller et login, der sammenligner de rå strenge, ser tre forskellige
navne.

Også dette site er afhængigt af kodepunkter, bare et mindre synligt sted. Noten
om
[understregningen, der gik gennem hvert bogstav](/da/notes/safari-underline-font-subset/)
beskriver, hvordan sitet serverer sin skrifttype i seks dele, tre vægte med
latinske tegn og tre med kyrilliske, så en ukrainsk side sættes i samme
skrifttype som en engelsk. Hver del angiver det tegnområde, den dækker, og
browseren henter kun de dele, en side rent faktisk har brug for. Den kyrilliske
erklæring angiver sit område i kodepunkter:

```text
unicode-range: U+0400-045F, U+0490-0491;
```

Det første område går fra Ѐ til џ og rummer de ukrainske Є, І og Ї sammen med
resten af det grundlæggende kyrilliske alfabet. Det andet rummer kun Ґ og ґ, der
ligger længere henne i den kyrilliske blok. Områderne bestemmer, hvad der
hentes, og noten konstaterer resultatet: engelske og danske sider henter tre
latinske filer og ingen kyrilliske.

## Hvor det går galt

**Længden tælles i kodeenheder.** JavaScripts `length` tæller UTF-16-enheder, så
𐌂 tæller som to, og et A med kombinerende ring tæller som to, hvor en læser ser
ét bogstav. En grænse, der håndhæves i enheder, afviser tekst, der sagtens kan
være der, og et snit ved en fast position kan dele et tegn:
`"A\u030A".slice(0, 1)` beholder A'et og taber ringen, og skærer man 𐌂 over på
midten, står der et enligt surrogat tilbage, som UTF-8-koderen erstatter med
U+FFFD, erstatningstegnet. Beslut for hver grænse, hvad den tæller: bytes,
hvor det er lagringen, der sætter grænsen, og tegn, som læseren ser dem, hvor
det er et menneske. `Intl.Segmenter` tæller på den sidste måde, og for Århus
skrevet med kombinerende ring giver den 5, hvor `length` giver 6. Klip kun
tekst ved de grænser, den melder.

**Ens tekst sammenlignes enhed for enhed.** De tre former af Å ovenfor er tre
forskellige strenge for `===`, for en hashfunktion og for et unikt indeks, så en
kunde kan oprette det samme navn to gange eller ikke finde et dokument ud fra
den titel, vedkommende skrev. Normalisér tekst til NFC, hvor den kommer ind i
systemet, gem den normaliseret, og sammenlign normaliserede former.
Stabilitetspolitikken garanterer, at en streng, der er normaliseret under én
version, forbliver normaliseret under alle senere, forudsat at strengen kun
indeholder tegn, som den version kender, så den kode, der normaliserer, skal
være lige så ny som teksten.

**Tegn, der ligner hinanden, går for at være ens.** Det latinske o, det
kyrilliske о og det græske ο er forskellige kodepunkter, som de fleste
skrifttyper tegner ens, så to kontonavne eller to varenumre kan se identiske ud
og afvige i et enkelt tegn. Unicode holder dem bevidst adskilt, og konsortiets
standard for sikkerhedsmekanismer, UTS #39, beskriver, hvordan den slags
forfalskning kan begrænses. Hvor et navn identificerer en person eller en
betalingsmodtager, så tillad kun én skrift pr. navn, eller tjek navnet mod
standardens sikkerhedsdata, før det godkendes.

**Bytes læses i den forkerte kodning.** Tekst, der er gemt som UTF-8 og læses
som windows-1252, forvandler blåbær til blÃ¥bÃ¦r, fordi hvert bogstav på to
bytes læses som to bogstaver på én byte hver. Ordet Unicode siger ikke, hvordan
de bytes blev skrevet; det gør kun kodningsformen. Angiv den overalt, hvor tekst
krydser en grænse, i HTTP-headeren, på siden, på databaseforbindelsen og i
filformatet, og behandl en fil, der kommer uden, som et spørgsmål til et
menneske og ikke som et gæt til koden.

**Teksten får skylden for en manglende glyf.** Når et bogstav vises som en tom
firkant, er årsagen som regel en skrifttype uden en form for det kodepunkt, og
selve teksten er intakt. Fordi Unicode overlader formerne til skrifttyperne,
skal hver skrifttype, et produkt bruger, dække alle de tegn, dets sprog har brug
for, de ukrainske Є, І, Ї og Ґ lige så vel som А til я, ellers tegner browseren
dem med den reserveskrift, den nu finder. Noten ovenfor giver netop det som
grunden til, at sitet overhovedet serverer kyrilliske dele: uden dem ville en
ukrainsk side falde tilbage på det, systemet nu tilbyder. Test hvert sprog med
hele dets alfabet frem for en enkelt eksempelsætning.

**Navne tjekkes mod A til Z.** Et mønster som `^[A-Za-z' -]+$` godkender O'Neill
og afviser Søren Ågård, Zoë og Ґалаґан, altså netop de navne, et dansk eller
ukrainsk produkt er til for. Afgør i stedet, om et tegn er et bogstav, ud fra
dets Unicode-egenskaber: i JavaScript godkender `/^[\p{L}\p{M}' -]+$/u` alle
fire. Egenskaberne kommer fra den kopi af Unicode Character Database, der ligger
i kørselsmiljøet, og Node.js 25 har version 16.0, så bogstaver, der er kommet
til i senere versioner, afvises, indtil kørselsmiljøet opgraderes. Validér navne
løst, og lad et menneske frem for et mønster afgøre de svære tilfælde.

## Læs mere

- [About the Unicode Standard](https://www.unicode.org/standard/standard.html)
  — konsortiets egen beskrivelse af standarden, hvad en version består af, og
  hvor den nyeste udgives.
- [The Unicode Standard, Chapter 2: General Structure](https://www.unicode.org/versions/Unicode18.0.0/core-spec/chapter-2/)
  — de ti designprincipper, koderummet, de tre kodningsformer og reglerne for
  ækvivalente sekvenser.
- [Unicode Character Encoding Stability Policies](https://www.unicode.org/policies/stability_policy.html)
  — det, der aldrig kan ændres, når et tegn først er kodet, fra kodepunktet og
  navnet til normaliseringen.
- [Unicode 18.0.0](https://www.unicode.org/versions/Unicode18.0.0/) — det, den
  aktuelle version tilføjer, og de tekniske standarder, der udgives i takt med
  den, blandt dem sortering og sikkerhed.

<https://engineer.company/da/glossary/unicode/>
