Jokainen näkyvä merkki PDF-tiedostossa sisältää viittauksen fonttiin, joka sen piirsi, ja PDFium Component antaa sinun seurata tätä viittausta takaisin fonttiobjektiin ja lukea, mitä se tietää. Pääsyn yksikkö on merkki, ei asiakirja: valitset merkin sen indeksillä sivun tekstissä ja pyydät perheen nimeä, perusnimeä, painoa, kursivointikulmaa ja sitä, onko taustalla oleva kirjasin todella mukana tiedoston sisällä. Tuo viimeinen ominaisuus on se, jota useimmat analyysit todella tavoittelevat, koska upotettu fontti kulkee asiakirjan mukana ja upottamaton fontti on vain lupaus siitä, että lukijan koneella sattuu olemaan sama kirjasin asennettuna
Komponentti paljastaa nämä samojen TPdf- ja TPdfView-objektien kautta, joita käytät hahmontamiseen ja tekstin purkamiseen. Avattavana ei ole erillistä "fonttitaulukko"-objektia. Kun sivun teksti on jäsennetty, fontin ominaisuudet riippuvat merkki-indeksistä, ja luet niitä yksi glyyfi kerrallaan. Tuo suunnittelu sopii siihen, miten PDF alun perinkin tallentaa tiedot: yksittäinen sivu voi vaihtaa fonttia kymmeniä kertoja, ja ainoa rehellinen vastaus kysymykseen "millä fontilla tämä asiakirja on" on "se riippuu siitä, mitä merkkiä tarkoitat"
Yhden merkin takana olevan fontin lukeminen
Pienin hyödyllinen toimenpide on ottaa merkki-indeksi ja tulostaa kaikki, mitä PDFium voi kertoa sen fontista. Jokainen fonttiominaisuus TPdf- ja TPdfView-objekteissa on indeksoitu merkkisijainnin mukaan, joten indeksi pujottelee niiden kaikkien läpi. Sivun on myös oltava nykyinen sivu, jotta indeksi ratkeaisi oikeaa tekstiä vasten, millä on merkitystä, kun siirryt sivun yksi ohi
procedure DescribeFontAt(Pdf: TPdf; CharIndex: Integer);
var
Report: TStringList;
PtSize: Single;
begin
Report := TStringList.Create;
try
PtSize := Pdf.FontSize[CharIndex];
Report.Add('Character : ' + Pdf.Character[CharIndex]);
Report.Add('Family : ' + Pdf.FontFamilyName[CharIndex]);
Report.Add('Base name : ' + Pdf.FontBaseName[CharIndex]);
Report.Add('Weight : ' + IntToStr(Pdf.FontWeight[CharIndex]));
Report.Add('Italic : ' + IntToStr(Pdf.FontItalicAngle[CharIndex]) + ' deg');
Report.Add('Size : ' + FormatFloat('0.0', PtSize) + ' pt');
Report.Add('Ascent : ' + FormatFloat('0.0', Pdf.FontAscent[CharIndex, PtSize]));
Report.Add('Descent : ' + FormatFloat('0.0', Pdf.FontDescent[CharIndex, PtSize]));
Report.Add('Embedded : ' + BoolToStr(Pdf.FontIsEmbedded[CharIndex], True));
ShowMessage(Report.Text);
finally
Report.Free;
end;
end;
Pari allekirjoitusta yllättää ihmisiä, jotka tulevat muista kirjastoista. FontAscent ja FontDescent ottavat kaksi argumenttia, merkki-indeksin ja pistekoon, koska PDFium raportoi nuo metriikat glyyfitila-yksiköissä, joista tulee pikseleitä vasta, kun skaalaat ne koolla, johon teksti oli asetettu. Anna arvo, jonka olet jo lukenut kohteesta FontSize[CharIndex], niin saat nousun (ascent) ja laskun (descent) samoissa pisteissä kuin muunkin asettelun. Lasku tulee takaisin negatiivisena, koska se mitataan peruslinjan alapuolelta. Perheen nimi ja perusnimi ovat tarkoituksella erillisiä merkkijonoja: perusnimi on raaka /BaseFont-merkintä PDF:stä, ja se kantaa usein osajoukon (subset) etuliitettä, kuten ABCDEF+, kun taas perheen nimi on siivottu nimi, johon hahmontaja (renderer) sen ratkaisee
Napsautuksen (click) muuttaminen merkki-indeksiksi
Katseluohjelmassa tiedät harvoin indeksiä etukäteen. Käyttäjä napsauttaa glyyfiä ja sinun on käännettävä pikselikoordinaatti sen alla olevaksi merkiksi. CharacterIndexAtPos tekee juuri sen, ottaen hiiren sijainnin ja toleranssin ja palauttaen lähimmän merkin indeksin, tai negatiivisen arvon, kun napsautus osui tyhjään tilaan (whitespace) tai tyhjälle sivulle
procedure TfrmMain.PdfViewMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
var
Index: Integer;
begin
if not PdfView.Active then
Exit;
// 4 px of slack in each direction so a near-miss still hits the glyph.
Index := PdfView.CharacterIndexAtPos(X, Y, 4.0, 4.0);
if Index < 0 then
Exit; // clicked between glyphs; leave the panel alone
PdfView.CurrentCharIndex := Index;
DescribeFontAt(PdfView.Pdf, Index);
end;
Toleranssia kannattaa hienosäätää. Liian tiukka ja käyttäjät tuntevat, että heidän on osuttava tarkasti kirjaimen varteen; liian löysä ja marginaaliin tehty napsautus napsahtaa johonkin kaukaiseen merkkiin, jolla ei ole mitään tekemistä sen kanssa, mitä he tarkoittivat. Kolmesta viiteen laitepikseliä on kohtuullinen lähtökohta näytön katseluun. Palautettu indeksi on nykyisen sivun jäsennettyyn tekstiin, samaan indeksiavaruuteen jota jokainen fonttiominaisuus odottaa, joten voit antaa sen suoraan yllä olevalle rutiinille. Sen tallentaminen kohteeseen CurrentCharIndex on valinnaista mutta kätevää: näkymä (view) pitää sen käsityksenään kohdistetusta glyyfistä, mikä on kätevää, jos käyttöliittymän (UI) muut osat haluavat lukea valinnan johtamatta sitä uudelleen
Upottaminen (Embedding) on ominaisuus, jolla on merkitystä
Useimmissa todellisissa töissä ainoa vastaamisen arvoinen kysymys on, onko jokainen fontti upotettu. Asiakirja, jonka kaikki fontit kulkevat sen sisällä, hahmontuu samalla tavalla painotalon (print bureau) RIP:ssä, kollegan kannettavassa tietokoneessa ja palvelimella, jossa ei ole GUI:ta ollenkaan. Asiakirja, joka nojaa upottamattomaan (non-embedded) Helvetica-fonttiin, pelaa uhkapeliä sen suhteen, että jokaisella noista koneista on vastaava kirjasin, ja kun uhkapeli epäonnistuu, lukija korvaa sen jollakin lähellä olevalla, metriikat siirtyvät ja huolellisesti aseteltu lomake juoksuttuu (reflows) juuri sen verran, että se hajoaa. Sivun tekstin läpikäynti ja fonttien lokeroiminen upottamisen tilan mukaan antaa sinulle tuon vastauksen halvalla
procedure ReportNonEmbeddedFonts(Pdf: TPdf);
var
Embedded, External: TStringList;
I: Integer;
Name: string;
begin
Embedded := TStringList.Create;
External := TStringList.Create;
try
Embedded.Sorted := True;
Embedded.Duplicates := dupIgnore;
External.Sorted := True;
External.Duplicates := dupIgnore;
for I := 0 to Pdf.CharacterCount - 1 do
begin
Name := Pdf.FontBaseName[I];
if Name = '' then
Continue; // generated spaces and the like have no font
if Pdf.FontIsEmbedded[I] then
Embedded.Add(Name)
else
External.Add(Name);
end;
if External.Count > 0 then
ShowMessage(IntToStr(External.Count) +
' non-embedded font(s):' + sLineBreak + External.Text)
else
ShowMessage('All ' + IntToStr(Embedded.Count) +
' font(s) on this page are embedded.');
finally
Embedded.Free;
External.Free;
end;
end;
Kaksi yksityiskohtaa pitää tämän rehellisenä. Ensinnäkin, CharacterCount on sivukohtainen, joten koko asiakirjan (whole-document) tarkastus tarkoittaa Pdf.PageNumber-ominaisuuden asettamista jokaiselle sivulle vuorotellen ja silmukan suorittamista uudelleen, yhdistäen tulokset. Toiseksi, tekstikerros sisältää generoituja merkkejä, kuten välilyöntejä, joita lukija päättelee sanojen väliin, ja niillä ei ole fonttiobjektia takanaan; tyhjän perusnimen tarkistus ohittaa ne sen sijaan, että kirjaisi haamun. Perusnimi on oikea avain deduplikointiin tässä, koska sen kantama osajoukon etuliite erottaa saman perheen kaksi eri osajoukkoa, mikä on yleensä se, mitä haluat tietää
Upotetun (embedded) kirjasimen vetäminen ulos
Kun fontti on upotettu, voit lukea sen tavut suoraan. FontData palauttaa raa'an fonttiohjelman, saman TrueType- tai CFF-tiedon jota PDF kantaa, mikä riittää kirjoittamaan erillisen (standalone) fonttitiedoston tai sormenjäljentämään (fingerprint) kirjasimen tunnettua kirjastoa vasten. Se palauttaa tyhjän taulukon (empty array), kun fonttia ei ole upotettu, joten upottamisen tarkistus ja pituuden tarkistus yhdessä suojaavat kirjoitusta
procedure SaveEmbeddedFont(Pdf: TPdf; CharIndex: Integer;
const OutputFile: string);
var
Data: TBytes;
Stream: TFileStream;
begin
if not Pdf.FontIsEmbedded[CharIndex] then
begin
ShowMessage('That glyph''s font is not embedded; nothing to extract.');
Exit;
end;
Data := Pdf.FontData[CharIndex];
if Length(Data) = 0 then
Exit;
Stream := TFileStream.Create(OutputFile, fmCreate);
try
Stream.WriteBuffer(Data[0], Length(Data));
finally
Stream.Free;
end;
ShowMessage('Wrote ' + IntToStr(Length(Data)) + ' bytes.');
end;
Tavut ovat upotettu osajoukko, eivät alkuperäinen vähittäismyyntifontti (retail font), joten se mitä saat takaisin kattaa yleensä vain ne glyyfit, joita asiakirja todella käytti. Se on aivan oikein forensiikkaan (forensics) ja varmentamiseen (verification) ja sopii huonosti uudelleenkäyttöön; Times New Roman -fontin osajoukko, joka sisältää kolmekymmentä glyyfiä, ei ole fontti, jonka voit asentaa ja jolla voit kirjoittaa. Käsittele purkamista (extraction) tapana tarkastaa mitä toimitettiin, ei fontin palautustyökaluna (font-recovery tool). Jos tarvitset vastaavan perusnimen tulosteen merkitsemiseen, lue FontBaseName[CharIndex] tiedon ohella, ja riisu (strip) johtava (leading) osajoukko-tunniste (subset tag), jos haluat paljaan (bare) perheen
Painonumeron (weight number) ymmärtäminen
FontWeight palauttaa numeerisen painoluokan, saman 100-900 -asteikon jota CSS käyttää, jossa 400 on normaali (regular) ja 700 on lihavoitu (bold). PDFium raportoi mitä tahansa fontti ilmoittaakin, mikä ei aina ole tasainen sataluku; kirjasin voi ilmoittaa 350 tai 650, ja minkä tahansa 600 tai sitä suuremman käsitteleminen "tarpeeksi lihavoituna ollakseen merkityksellinen" kestää paremmin kuin tasan 700:n testaaminen. Kursivointikulma (italic angle) on kumppanisignaali: nollasta poikkeava arvo, yleensä negatiivinen, tarkoittaa, että kirjasin on vino (oblique) tai aito kursivoitu (true italic) muotoilu, ja nolla tarkoittaa pystyä (upright). Yhdessä ne antavat sinun erottaa lihavoidun-kursivoidun jakson normaalista ilman, että hahmonnat mitään, mikä on sellainen tarkistus, jonka esitarkastusajo (preflight pass) tai saavutettavuuden auditointi (accessibility audit) haluaa tehdä massana (in bulk)
Mikään näistä luvuista ei vaadi hahmonnettua bittikarttaa. Ne tulevat jäsennetystä tekstikerroksesta, joten avoin asiakirja oikealla sivulla on kaikki tarvitsemasi asetus, mikä tekee fonttien tarkastamisesta halpaa suorittaa koko arkiston yli. Jos yhdistät tämän tekstin purkamiseen, samat merkki-indeksit asettuvat riviin ulos vetämäsi tekstin kanssa, joten glyyfin fontti ja sen Unicode-arvo ovat kaksi lukua yhtä indeksiä vasten. Kumppaniartikkeli (companion article on) tekstin purkamisesta (extracting text from PDF documents with PDFium Component) PDF-asiakirjoista PDFium Component -komponentilla käsittelee (covers) tekstikerroksen tuota puolta perusteellisemmin
Tässä esitetyt fonttiominaisuudet ovat osa PDFium Delphi VCL Component -komponenttia