Så fungerar textredigering i en PDF: innehållsströmmar, teckensnittskodningar och varför du bara kan ändra en rad i taget
En PDF-sida har inga stycken eller textrutor, bara instruktioner om att rita glyfer med ett teckensnitt på en position. När en rad redigeras raderas de gamla instruktionerna och nya skrivs på samma ställe, och det avgör vad som går att redigera och vad som inte gör det.

En PDF-sida har inga stycken och inga textrutor. Den har en lista med instruktioner: på den här positionen, med det här teckensnittet och den här storleken, rita de här glyferna. Att ändra text betyder att radera de instruktioner som ritar raden och skriva nya på samma position, med samma teckensnitt, storlek och färg. Därför redigerar Redigera PDF en rad i taget och kan inte radbryta text: det följer av hur texten lagras.
Hur en textrad ser ut i filen
Sidans innehåll ligger i en innehållsström, ett litet program av operatorer. Fyra operatorer gör det mesta av jobbet för text: BT och ET omsluter ett textblock, Tf väljer teckensnitt och storlek, Td flyttar till nästa position och Tj ritar en sträng. Med ett standardteckensnitt ser det enklaste fallet ut så här:
BT /F1 18 Tf 20 150 Td (Hello World) Tj ET
Det betyder: välj teckensnitt F1 i 18 punkter, gå till x 20, y 150 och rita Hello World. En PDF som skrivits ut från Chrome ser oftast annorlunda ut: inom parenteserna står inte längre tecken utan glyfnummer från teckensnittet, och Tm sätter positionen en gång. Nedan syns raden Hello World from Georgia från en sida som exporterats av Chrome, där varje grupp om fyra hexadecimala siffror är ett glyfnummer:
BT /F4 24 Tf 1 0 0 -1 30 52 Tm <002b0048004f004f00520003003a00520055004f0047000300490055005200500003002a004800520055004a004c0044> Tj ET
002b är H, 0048 är e, 004f är l, 0003 är ett mellanslag, 003a är W och så vidare. De här numren betyder något bara inom just det här teckensnittet; de är inte Unicode. När du kopierar texten översätter läsaren numren tillbaka till tecken med en uppslagstabell som ligger på ett annat ställe i filen.
Två saker följer av det. För det första står det ingenstans i innehållsströmmen "det här är ett stycke": ett radbyte är bara ytterligare en Td eller Tm som flyttar positionen till nästa rad, och hur många rader ett stycke har måste utläsas ur positionerna. För det andra krävs för att skriva ett nytt tecken i en rad det teckenets nummer i det teckensnittet, och teckensnittet innehåller ofta bara de glyfer som användes när filen skrevs. Det är resultatet av teckensnittssubsetting.
Du kan expandera strömmarna med qpdf:
qpdf --qdf --object-streams=disable in.pdf out.pdf
Öppna out.pdf i en textredigerare och sök efter Tj för att se varje sträng som ritas på sidan. Vad en PDF i övrigt innehåller beskrivs i Vad som finns i en PDF.
Hur redigeraren bygger upp en "rad" av glyfer
Redigeraren måste först gruppera lösa glyfer till en rad som användaren kan klicka på. Glyfer med samma teckensnitt, storlek och färg, vars avstånd till föregående glyf är högst en teckenstorlek, hör till samma textsegment, en grupp angränsande glyfer som redigeraren behandlar som en enhet; ett radbyte, ett nytt teckensnitt eller en ny färg avslutar det. Text som löper från höger till vänster eller lodrätt grupperas inte och går inte att öppna för redigering.
Två följder går att se direkt. En rad som blandar två teckensnitt, till exempel en kinesisk rad med ett engelskt ord i, delas upp i två intilliggande textsegment som klickas på var för sig. Efter en redigering händer samma sak: om den nya texten använder ett annat teckensnitt blir raden två textsegment, och redigering av det ena lämnar den andra oförändrad.
Redigera en rad: radera den gamla texten, skriv sedan den nya
Raderingen använder den rena textformen av svärtning: glyferna inom radens område tas bort ur innehållsströmmen, bilder och linjer lämnas ifred, och ingen färgad ruta ritas över den gamla texten, så att kopiering och sökning inte längre hittar den. Det ändrar bara sidans innehåll; om samma text också finns i en kommentar eller i dokumentegenskaperna påverkas de inte.
Skrivningen lägger till en ny textinstruktion i slutet av sidans innehållsström, med utgångspunkt i den ursprungliga radens baslinjeposition och med dess storlek och färg. Efter att raden ovan ändrats till Hello World 2 läggs detta till i slutet av sidan:
q 1 0 0 1 22.5 148.92 cm 0 0 0 rg
/Span <</ActualText <feff00480065006c006c006f00200057006f0072006c006400200032>>> BDC
BT /F4 18 Tf 0 0 Td <002b0048004f004f00520003003a00520055004f00470003> Tj ET
BT /F9 18 Tf 102.18 0 Td (2) Tj ET
EMC
Q
Det första BT använder det ursprungliga teckensnittet F4 och de ursprungliga glyfnumren, så att Hello World återanvänds precis som det var. "2" finns inte i det ursprungliga teckensnittets subset, så det andra BT startar ett nytt standardteckensnitt, Helvetica (F9), för att rita det. q och Q avgränsar blockets koordinattransformation och färg till blocket självt; redigeraren lägger dessutom det ursprungliga innehållet i ett q/Q-par, så att ett tillstånd som lämnats obalanserat i slutet av den ursprungliga sidan inte kan påverka den nya texten. Så här såg ett textsegment ut; siffrorna varierar mellan filer.
Att lägga till i slutet har tre följder:
- Den nya texten målas sist, ovanpå allt annat på sidan. Text som tidigare låg under en bild eller ett färgat block hamnar ovanför det efter en redigering.
- Ett verktyg som extraherar text i innehållsströmmens ordning (som
pdftotext -raw) läser den redigerade raden sist på sidan, medan extrahering efter position (pdftotext -layout) inte visar någon ändring. Sidan ser likadan ut, men läsordningen har ändrats. - Redigeraren fäster en ActualText vid den nya texten, som talar om för en läsare vilka verkliga tecken glyferna står för. En glyf kan motsvara flera tecken, och uppslagning enbart på glyfnumret ger ibland ett kompatibilitetstecken (till exempel ger 文 Kangxi-radikalen ⽂); ActualText ser till att det du kopierar är det du skrev.
Vilket teckensnitt som ritar den nya texten
Att återanvända det ursprungliga teckensnittet är det säkraste, eftersom glyfernas form, vikt och bredd stämmer med det som står bredvid. Om det går att göra beror på om redigeraren kan avgöra vilken kod ett tecken har i det teckensnittet:
- Ett sammansatt teckensnitt som det ovan (Type0, tvåbytes glyfnummer, kodningen Identity-H): slå upp tecknet i teckensnittet för att få glyfnumret och använd det om det hittas. Hittas det inte, finns tecknet inte i subsetet.
- Ett enkelt enbytesteckensnitt (TrueType, Type1) med kodningen WinAnsi eller MacRoman: kopplingen från tecken till byte är fast, och breddvärdena kommer från breddtabellen i filen.
- Ett teckensnitt med anpassad kodning (Differences) eller ett som förlitar sig på teckensnittets inbyggda kodning: det finns ingen tillförlitlig koppling, så redigeraren gissar inte utan går till ett reservteckensnitt, annars skulle texten bli oläslig.
När ett tecken inte finns i det ursprungliga teckensnittet används ett reservteckensnitt. Vanliga latinska bokstäver använder det närmaste av Helvetica, Times och Courier, standardteckensnitt som varje läsare har, så ingenting behöver bäddas in. Allt annat, till exempel kinesiska tecken, kana, hangul, thai, latinska bokstäver med accent, kyrilliska och grekiska, använder ett teckensnitt med öppen källkod ur Noto-familjen: servern tar tecknen i begäran och klipper ut just de glyferna ur det fullständiga teckensnittet med subsettingbiblioteket HarfBuzz, som är öppen källkod, oftast några få KB, och webbläsaren bäddar in dem i din fil. Den regionala formen hos ett kinesiskt tecken följer gränssnittets språk: förenklad, traditionell, japansk eller koreansk.
Glyferna bäddas in i stället för att bara skriva ett teckensnittsnamn, eftersom ett teckensnitt som refereras enbart med namn ersätts av det reservteckensnitt som respektive läsare väljer, och vikten kan skilja sig mellan läsare; när glyferna är inbäddade visar varje läsare samma former.
Varför bara en rad i taget
Redigeraren har ingen layoutmotor. Varje glyfposition är fast i filen; den nya texten läggs helt enkelt ut åt höger från startpunkten med teckensnittets bredder, utan någon logik för radbrytning vid kanten, och inget efter den flyttas. Gör du en rad längre fortsätter de extra tecknen åt höger, och det som hamnar utanför sidkanten syns inte. Att skriva om ett stycke eller lägga till rader i en tabell kräver en ny layout, vilket betyder att gå tillbaka till källfilen som PDF:en skapades från.
Bredderna är dessutom bara teckensnittets egna avancebredder (hur långt skrivpositionen flyttas efter varje glyf), utan kerning och utan glyfersättning. Text som inte behöver shaping, alltså kontextberoende val och placering av glyfer (latinsk, kyrillisk, grekisk skrift och kinesiska tecken) läggs ut korrekt. Thailändska tonmärken behöver glyfpositionering för att hamna rätt, vilket redigeraren inte gör, så de kan hamna fel. Arabiska kräver kontextberoende glyfformer och layout från höger till vänster, hebreiska kräver layout från höger till vänster, och indiska skriftsystem kräver att konsonanter och vokaltecken kombineras till glyfer; allt detta kräver full shaping, vilket redigeraren inte gör i dag, så inmatningen avvisas i stället för att ge fel tecken.
Två gränser till kommer från själva filen. Innehållet på en inskannad sida är en enda bild utan textoperatorer, så det finns inget att radera eller ändra. När en fil med digital signatur har redigerats har det ursprungliga innehåll som signaturen skyddade ändrats, och signaturen är inte längre giltig.
Kontrollera själv
Med tre kommandon kan du se de beskrivna ändringarna i en nedladdad fil:
pdftotext -raw edited.pdf - # extrahera i innehållsströmmens ordning: den gamla texten är borta, den redigerade raden kommer sist
pdffonts edited.pdf # teckensnittslista: de ursprungliga teckensnitt som återanvändes och det nyinbäddade teckensnittssubsetet
qpdf --qdf --object-streams=disable edited.pdf edited.qdf # expandera innehållsströmmen; det tillagda BT ... ET-blocket ligger sist
Läs vidare
- Varför teckensnittssubsetting bryter redigering men inte läsning: varför det ursprungliga teckensnittet kan sakna ett tecken och vad som händer då.
- Vad som finns i en PDF: objekt, strömmar och korsreferenstabellen: helhetsbilden av innehållsströmmar, objekt och filstruktur.
- Redigera text i en PDF online: rätta utan källfilen: hur du använder det, vad det passar för och när du bör gå tillbaka till källfilen.