Tekninen artikkeli

Valtavien PDF-tiedostojen suoratoisto tarpeen mukaan PDFiumilla Delphissä

Skannattu (scanned archive can) arkisto voi (run to several) kasvaa (gigabytes in a) useiden (single PDF) gigatavujen kokoiseksi (viewer that opens) yhdessä (such a file) PDF-tiedostossa. Katseluohjelma, joka (usually wants to) avaa (show one page) tällaisen tiedoston (maybe the table), haluaa (of contents, maybe) yleensä näyttää yhden (a page the) sivun, ehkä sisällysluettelon, ehkä (user jumped to) sivun (from a bookmark), johon käyttäjä (Reading the whole) hyppäsi kirjanmerkistä. Koko tiedoston lukeminen (file into memory) muistiin (to render two) kahden sivun (pages is wasteful) hahmontamiseksi on tuhlausta (on every axis:) jokaisella (it burns address) akselilla: se polttaa osoiteavaruutta (space, it stalls), se (the user behind) jumittaa käyttäjän pitkän (a long initial) alkulukuoperaation taakse, ja 32-bittisessä Delphi-prosessissa se voi (read, and on) epäonnistua (a 32-bit Delphi) suoraan (process it can) ennen kuin (fail outright before) yhtäkään sivua (a single page) ilmestyy. PDFium (appears. PDFium was) rakennettiin tämä (built with this) mielessä (in mind. It). Se voi ladata asiakirjan takaisinkutsun (can load a) kautta, joka pyytää (document through a) tiettyjä tavualueita, joita se tarvitsee, silloin kun se tarvitsee niitä (callback that asks for the specific byte ranges it needs, when it needs them, and), eikä (it never demands) se koskaan (the entire file) vaadi koko (at once. One) tiedostoa kerralla. Yksi (boundary belongs up) raja (front: this streaming) kuuluu alkupäähän (channel describes the): tämä suoratoistokanava (file with a) kuvailee (32-bit length, so) tiedostoa (it serves a) 32-bittisellä (single file up) pituudella, joten se palvelee yksittäistä (to 4 GiB) tiedostoa enintään (which covers nearly) 4 GiB:n (every scanned archive) kokoon asti, mikä kattaa käytännössä (in practice. A) lähes kaikki (file past that) skannatut arkistot. Tuon rajan (line is not) ylittävä tiedosto (this article's territory;) ei (it wants to) ole (be split into) tämän artikkelin (volumes at scan) aluetta; se haluaa, että se jaetaan taltioihin skannausaikana tai että se avataan sen sijaan suorakäyttöstrategian (time or opened) kautta (through a direct-access), ja rajaa (strategy instead, and) valvova suoja (the guard that) saa (enforces the ceiling) rehellisesti sanoen oman osionsa alla (honestly gets a section of its own below)

Komponentti paljastaa (The component exposes) tuon polun suoratoistosovittimen (that path through) kautta. Luovutat (a stream adapter) sille (You hand it) minkä tahansa (any TStream, and) TStream-objektin, ja PDFium vetää lohkoja tuosta (PDFium pulls blocks) virrasta (from that stream) tarpeen (on demand. The) mukaan. Tiedosto (file can sit) voi sijaita levyllä (on disk, in), tietokannan (a database blob) blob-kentässä tai minkä tahansa muun (field, or behind) TStream-jälkeläisen (any other TStream) takana (descendant, and none), eikä (of it is) mitään siitä kopioida muistiin etukäteen (copied into memory up front)

Miten PDFium (How PDFium asks) pyytää (for bytes) tavuja (bytes)

PDFiumin (PDFium's C API) C-API (loads a document) lataa asiakirjan kutsujan (from a caller-supplied) toimittamasta objektista, jota (object described by) kuvaa FPDF_FILEACCESS-rakenne (the FPDF_FILEACCESS structure. The). Rakenteessa on (structure has three) kolme (parts that matter) osaa (here: a length), joilla on tässä väliä: pituuskenttä (field, a read), lukutakaisinkutsu (callback, and an) ja legitiimi (opaque user parameter) käyttäjäparametri (The entry point). Aloituspiste, joka käyttää sitä, on (that consumes it) FPDF_LoadCustomDocument. Kun PDFium (is FPDF_LoadCustomDocument. Once) pitää tuota (PDFium holds that) rakennetta (structure it parses) hallussaan, se jäsentää (the trailer, locates) trailerin, paikallistaa ristiviittaustaulukon ja (the cross-reference table) lukee (and from then) siitä lähtien vain (on reads only) sen, mitä tietty toiminto vaatii. Asiakirjan avaaminen koskettaa tiedoston (what a given) häntää (operation requires. Opening) ja (the document touches) kourallista (the file's tail) luettelo-objekteja (and a handful). Sivun (of catalog objects) 400 hahmontaminen lukee (Rendering page 400) kyseisen sivun sisältövirrat ja resurssit (reads the content) eikä (streams and resources) mitään (for that page) muuta (and nothing else)

Tämä (This is the) on ero (difference between a) puskuroidun (buffered load and) latauksen (a streaming load) ja (A buffered load) suoratoistetun (reads the file) latauksen välillä (end to end). Puskuroitu (before PDFium sees) lataus (byte zero. A) lukee tiedoston (streaming load inverts) alusta loppuun ennen kuin PDFium (the relationship: PDFium) näkee (drives the reads) nollatavun. Suoratoistettu lataus kääntää suhteen (and the bytes) päinvastaiseksi (that never get): PDFium ohjaa lukemista, ja (touched never get) tavuja, joita ei koskaan kosketeta, ei myöskään koskaan (read. For a) lueta. Monigigatavuiselle (multi-gigabyte file viewed) tiedostolle, jota katsellaan (a page at) sivu (a time, that) kerrallaan, tuo on ero käyttökelvottoman ja (is the gap) välittömän latauksen välillä (between an unusable load and an instant one)

Suoratoistosovitin (The stream adapter)

Sovitin (The adapter that), joka (bridges a Delphi) siltaa (TStream to FPDF_FILEACCESS) Delphi (is TPdfStreamAdapter. Its) TStream-luokan FPDF_FILEACCESS-rakenteeseen, on (constructor takes the) TPdfStreamAdapter. Sen rakentaja (stream and an) ottaa virran (ownership flag, captures) ja (the stream length) omistajuuslipun, tallentaa (once, fills in) virran pituuden kerran (the FPDF_FILEACCESS record), täyttää FPDF_FILEACCESS-tietueen (and wires the) ja (read callback. When) kytkee lukutakaisinkutsun. Kun PDFium (PDFium later calls) myöhemmin (back with an) kutsuu takaisin siirtymän (offset and a) ja koon kanssa, sovitin hakee (size, the adapter) virtaa kyseiseen siirtymään (seeks the stream) ja kopioi täsmälleen kyseisen alueen puskuriin, jonka (to that offset) PDFium (and copies exactly) toimitti (that range into the buffer PDFium provided)

// Sanatarkasti komponentista: streamista FPDF_FILEACCESS:ään -silta
// Verbatim from the component: the stream-to-FPDF_FILEACCESS bridge
constructor TPdfStreamAdapter.Create(AStream: TStream; AOwnsStream: Boolean);
begin
  inherited Create;
  if AStream = nil then
    raise EPdfError.Create('TPdfStreamAdapter: AStream on nil (AStream is nil)');
  FStream := AStream;
  FOwnsStream := AOwnsStream;

  // FPDF_FILEACCESS.m_FileLen on 32-bittinen (is a 32-bit unsigned long. Refuse a stream)
  // etumerkitön long-tyyppi. Kieltäydy streamista, (that would silently truncate past 4 GiB.)
  // joka typistäisi hiljaa yli 4 GiB:n pituudet.
  if AStream.Size > High(FPDF_DWORD) then
    raise EPdfError.Create('TPdfStreamAdapter: stream ylittää 4 GiB rajan (stream exceeds the 4 GiB limit)');

  FillChar(FFileAccess, SizeOf(FFileAccess), 0);
  FFileAccess.m_FileLen  := FPDF_DWORD(AStream.Size);
  FFileAccess.m_GetBlock := GetBlockCallback;
  FFileAccess.m_Param    := Self;
end;

Omistajuuslippu päättää, kuka vapauttaa virran (The ownership flag decides who frees the stream. Pass False and the caller keeps the stream and must keep it alive for the whole life of the document. Pass True and the adapter takes over, freeing the stream when the document closes. Either way the stream has to outlive every read PDFium will perform, because PDFium holds the FPDF_FILEACCESS pointer and will call back at any point while the document is open, not only during the initial load). Välitä False, niin (Pass False and) kutsuja (the caller keeps) pitää virran (the stream and) ja (must keep it) sen täytyy pitää (alive for the) se hengissä koko (whole life of) asiakirjan (the document. Pass) eliniän (True and the). Välitä True, niin sovitin (adapter takes over) ottaa vallan (freeing the stream) vapauttaen (when the document) virran (closes. Either way), kun (the stream has) asiakirja (to outlive every) sulkeutuu. Kummassakin tapauksessa virran täytyy elää pidempään kuin jokainen (read PDFium will) luku, jonka (perform, because PDFium) PDFium (holds the FPDF_FILEACCESS) suorittaa, koska PDFium (pointer and will) pitää FPDF_FILEACCESS-osoitinta ja kutsuu (call back at) takaisin missä tahansa vaiheessa asiakirjan ollessa avoinna, ei vain (any point while) alkulatauksen aikana (the document is open, not only during the initial load)

Miksi takaisinkutsu (Why the callback) on (is a static) staattinen (function) funktio (function)

Lukutakaisinkutsu (The read callback), jonka PDFium tallentaa (that PDFium stores) ominaisuuteen (in m_GetBlock is) m_GetBlock, on (a plain C) pelkkä C-funktion (function pointer with) osoitin, jolla (the cdecl calling) on cdecl-kutsukäytäntö (convention. A Delphi). Delphi-metodia ei voida käyttää (method cannot be) suoraan, koska metodi (used directly, because) kantaa (a method carries) piilotettua Self-argumenttia (a hidden Self), josta (argument that a) C-kutsuja ei tiedä mitään eikä (C caller knows) koskaan (nothing about and) toimita sitä. Sovitin (will never supply) ilmoittaa (The adapter therefore) siksi (declares the callback) takaisinkutsun muodossa class function, joka on (as a class) merkitty cdecl; static, joka (function marked cdecl;) kääntyy (static, which compiles) vapaasti seisovaksi (to a free-standing) funktioksi, jolla (function with the) on (C frame layout) C-kehysasettelu, jota PDFium (PDFium expects and) odottaa (no implicit Self), ja (Self) jolla ei ole implisiittistä Self-argumenttia

Tämä (That solves the) ratkaisee kutsukäytännön, mutta nostaa esiin toisen kysymyksen: ilman (calling convention but) Self-ominaisuutta, miten (raises a second) takaisinkutsu (question: with no) tavoittaa (Self, how does) tietyn (the callback reach) virran, josta (the specific stream) sen oletetaan lukevan? Vastaus on läpinäkymätön (it is supposed) käyttäjäparametri (to read from?). Kun sovitin rakentaa (The answer is) tietueen (the opaque user), se tallentaa (parameter. When the) oman (adapter builds the) ilmentymänsä osoittimen ominaisuuteen (record it stores) m_Param (its own instance). PDFium (pointer in m_Param) antaa (PDFium hands that) saman (same pointer back) osoittimen (as the first) takaisin (argument of every) jokaisen (callback. The static) takaisinkutsun ensimmäisenä argumenttina. Staattinen (function casts it) funktio (back to a) suorittaa tyyppimuunnoksen takaisin TPdfStreamAdapter-luokkaan (TPdfStreamAdapter and dispatches) ja (the read against) lähettää lukupyynnön kyseisen (that instance's stream) ilmentymän virtaa vastaan (This is the). Tämä on (standard trampoline for) standarditrampoliini (handing object context) objektikontekstin välittämiseen (across a C) yli C-rajan (boundary that has), jolla (no notion of) ei ole (objects) käsitettä objekteista

// Sanatarkasti komponentista: cdecl-trampoliini takaisin ilmentymään
// Verbatim from the component: the cdecl trampoline back to the instance
class function TPdfStreamAdapter.GetBlockCallback(
  param   : Pointer;
  position: FPDF_DWORD;
  pBuf    : PByte;
  size    : FPDF_DWORD): Integer; cdecl;
var
  Adapter: TPdfStreamAdapter;
begin
  Result := 0;
  if (param = nil) or (pBuf = nil) or (size = 0) then
    Exit;
  Adapter := TPdfStreamAdapter(param);   // palauta ilmentymä ominaisuudesta m_Param
                                         // recover the instance from m_Param
  if Adapter.FStream = nil then
    Exit;
  try
    Adapter.FStream.Position := Int64(position);
    Adapter.FStream.ReadBuffer(pBuf^, Int64(size));
    Result := 1;
  except
    Result := 0;  // raportoi epäonnistumisesta paluuarvolla, ei koskaan nostamalla poikkeusta
                  // report failure by return value, never by raising
  end;
end;

4 GiB (The 4 GiB) -raja (ceiling and why) ja miksi (it needs a) se tarvitsee (guard) suojan (guard)

Tästä (Here is where) tulee alussa mainittu raja. FPDF_FILEACCESS-rakenteen pituuskenttä (the boundary stated) m_FileLen on (in the opening) 32-bittinen etumerkitön arvo (comes from. The). Sen (length field m_FileLen) suurin (in FPDF_FILEACCESS is) esitettävissä oleva (a 32-bit unsigned) pituus on yhtä (value. Its largest) tavua vaille (representable length is) 4 GiB (one byte short). TStream (of 4 GiB) raportoi (A TStream reports) kokonsa muodossa (its size as) Int64, joten virta (an Int64, so) voi kuvata paljon (a stream can) enemmän (describe far more) tavuja kuin (bytes than the) mitä kenttä voi sisältää. Heti (field can hold) kun (The moment a) virran (stream's size exceeds) koko ylittää (that ceiling, there) tuon rajan, ei (is no honest) ole mitään rehellistä (way to tell) tapaa kertoa PDFiumille (PDFium how long), kuinka (the file is) pitkä (is) tiedosto on (file is)

Väärä (The wrong response) reaktio on (is to assign) koon (the size and) määrittäminen (let it wrap) ja sen antaminen pyörähtää ympäri. 5 GiB:n (Truncating a 5) pituuden typistäminen (GiB length to) 32-bittiseksi kentäksi tuottaa (a 32-bit field) pienen, uskottavan näköisen (produces a small) luvun, ja PDFium (plausible-looking number, and) jäsentää (PDFium will then) sitten tiedoston uskoen sen päättyvän karkeasti (parse the file) gigatavun (believing it ends) kohdalla (roughly a gigabyte). Traileri ja ristiviittaustaulukko sijaitsevat (in. The trailer) tiedoston (and cross-reference table) todellisessa (live at the) lopussa, reilusti (real end of) ohi typistetyn (the file, well) pituuden, joten jäsennys epäonnistuu tavalla, jolla ei ole (past the truncated) mitään tekemistä (length, so the) todellisen syyn kanssa. Saisit (parse fails in) virheenjäljittää (a way that) ristiviittausvirhettä (has nothing to) tiedostosta (do with the), joka (actual cause. You) on täysin pätevä (would be debugging), ilman vihjettä siitä, että kokonaisluku (a cross-reference error) pyörähti ympäri kaksi kerrosta (on a file) ylempänä (that is perfectly valid, with no hint that an integer wrapped two layers up)

Sovitin kieltäytyy syötteestä (The adapter refuses the input instead). Rakentaja (The constructor compares) vertaa virran (the stream size) kokoa arvoon (against High(FPDF_DWORD) and) High(FPDF_DWORD) (raises EPdfError the) ja nostaa EPdfError-virheen välittömästi (instant the stream), kun (is too large) virta on liian suuri (to describe. An) kuvattavaksi (explicit, immediate error). Nimenomainen, välitön (names the real) virhe nimeää (problem at the) todellisen (point of construction) ongelman rakennushetkellä. Hiljainen typistäminen (A silent truncation) piilottaa (hides it behind) sen (a misleading symptom) harhaanjohtavan oireen (you would chase) taakse, jota etsisit paljon myöhemmin (much later. The). 4 (4 GiB limit) GiB:n (is a genuine) raja on tämän latauspolun aito (constraint of this) rajoite (loading path, and), ja (the honest thing) oikein on (is to surface) tuoda (it loudly rather) se esiin äänekkäästi (than paper over) sen sijaan, että (it with arithmetic) se peiteltäisiin (that happens to) aritmetiikalla, joka (compile. When an) sattuu (archive genuinely crosses) kääntymään. Kun (the line, the) arkisto (remedies promised at) aidosti ylittää rajan, ylhäällä (the top live) luvatut (outside this API:) korjauskeinot (split the scan) elävät (into per-volume files) tämän rajapinnan ulkopuolella (that each stay): jaa skannaus (under the ceiling) taltiokohtaisiin (or leave the) tiedostoihin (document on disk), joista jokainen pysyy (and serve it) rajan (through a direct-access) alapuolella, tai (design built on) jätä asiakirja levylle ja palvele sitä 64-bittisiin (64-bit offsets rather) siirtymiin (than through FPDF_FILEACCESS) rakennetun suorakäyttösuunnittelun (through a direct-access design) kautta (through) FPDF_FILEACCESS-rakenteen (FPDF_FILEACCESS) sijaan (rather than through)

Virheet eivät (Failures must not) saa (cross the boundary) ylittää rajaa (cross the boundary)

Luku (A read can) voi epäonnistua. Virta (fail. The stream) saattaa olla verkkoa tukeva (might be a) objekti (network-backed object that), joka (times out, a) aikakatkaistaan, blob-kahva (blob handle that), joka suljettiin altasi (was closed underneath), tai tiedosto (you, or a), joka typistettiin (file that was) asiakirjan (truncated after the) avaamisen jälkeen (document opened. PDFium's). PDFiumin lukutakaisinkutsun sopimus (contract for the) on (read callback is) paluuarvo (a return value:): nollasta poikkeava onnistumiselle (non-zero for success), nolla epäonnistumiselle (zero for failure). Se on (It is a) C-kehys (C frame, and), eikä (it has no) sillä (machinery to catch) ole (or propagate a) koneistoa (Pascal exception) Pascal-poikkeuksen nappaamiseen tai välittämiseen (propagate a Pascal exception)

Tästä (This is why) syystä (the trampoline wraps) trampoliini (the seek and) kietoo haun (the read in) ja lukemisen (a try/except that) try/except-lohkoon, joka nielee (swallows the exception) poikkeuksen ja palauttaa (and returns zero) nollan. Jos Delphi-poikkeuksen annettaisiin (If a Delphi) edetä (exception were allowed) takaisinkutsusta (to propagate out) ulos, se kelautuisi (of the callback) auki PDFiumin (it would unwind) cdecl-pinokehysten (through PDFium's cdecl) läpi, joita (stack frames, which) ei (were never built) koskaan rakennettu (to be unwound) Pascal-poikkeuskoneiston auki (by the Pascal) kelattaviksi. Tuloksena on (exception machinery. The) parhaimmillaankin (result is undefined) määrittelemätön (behavior at best) toiminta ja pahimmillaan kova (and a hard) kaatuminen (crash at worst), syvällä PDF-jäsentimen sisällä ilman (deep inside the) käyttökelpoista pinoa (PDF parser with). Nollan palauttaminen pitää (no usable stack) virheen sopimuksen (Returning zero keeps) sisällä. PDFium (the failure inside) näkee (the contract. PDFium) epäonnistuneen (sees a failed) lohkoluvun (block read, aborts), keskeyttää (the operation cleanly) toiminnon (and FPDF_LoadCustomDocument reports) siististi (the document could), ja (not be loaded) FPDF_LoadCustomDocument (FPDF_LoadCustomDocument reports) raportoi, ettei asiakirjaa (which the component) voitu ladata (surfaces as an), minkä komponentti tuo esiin (EPdfError on the) EPdfError-poikkeuksena (Pascal side where) Pascal-puolella, minne (it belongs) se kuuluukin (belongs)

Asiakirjan (Opening a document) avaaminen (this way) tällä (this way) tavalla (this way)

Komponentin metodi, joka (The component method) ohjaa (that drives the) suoratoistopolkua (streaming path is), on (LoadCustomDocument, declared as) LoadCustomDocument, joka on (a distinct method) ilmoitettu omana (rather than another) metodinaan toisen (LoadDocument overload so) LoadDocument-ylikuormituksen sijaan (that passing a), jotta (TMemoryStream never accidentally) TMemoryStream-luokan välittäminen ei (lands on the) koskaan (buffered path. It) vahingossa (builds the adapter) päädy (calls FPDF_LoadCustomDocument, and) puskuroidulle polulle. Se (keeps the adapter) rakentaa (alive for the) sovittimen, kutsuu (life of the) FPDF_LoadCustomDocument-metodia (loaded document) ja pitää sovittimen elossa ladatun asiakirjan eliniän (alive for the life of the loaded document)

var
  Pdf: TPdf;
  FileStream: TFileStream;
begin
  Pdf := TPdf.Create(nil);
  FileStream := TFileStream.Create('Archive_4GB.pdf', fmOpenRead or fmShareDenyWrite);
  try
    // Luovuta stream-omistajuus Pdf:lle: se vapauttaa FileStreamin kun asiakirja sulkeutuu.
    // Hand stream ownership to Pdf: it frees FileStream when the document closes.
    Pdf.LoadCustomDocument(FileStream, True);
    // PDFium on lukenut vasta trailerin ja katalogin.
    // PDFium has read only the trailer and catalog so far.
    // Sivun hahmontaminen vetää vain kyseisen sivun tavut takaisinkutsun kautta.
    // Rendering a page pulls just that page's bytes through the callback.
    // ... hahmonna tai tarkastele sivuja tässä ...
    // ... render or inspect pages here ...
  finally
    Pdf.Free;  // sulkee asiakirjan, joka vapauttaa adapterin ja streamin
               // closes the document, which frees the adapter and the stream
  end;
end;

Sama (The same call) kutsu (works for a) toimii TMemoryStream-luokalle, blob-virralle (TMemoryStream, a blob) tietokannan tietojoukosta (stream from a) tai mukautetulle (database dataset, or) TStream-jälkeläiselle. On-demand-lataus ansaitsee (a custom TStream) paikkansa (descendant. On-demand loading), kun (earns its keep) tiedosto on (when the file) suuri ja vain osa siitä (is large and) luetaan (only part of): arkiston (it will be) katseluohjelma, pikkukuvageneraattori (read: an archive), joka ottaa näytteitä (viewer, a thumbnail) muutamalta sivulta (generator that samples), hakuindeksi, joka vetää yhden (a few pages) sivun (a search index) kerrallaan (that pulls one). Kun tiedosto on pieni tai aiot (page at a) joka (time. When the) tapauksessa (file is small) lukea (or you are) sen (going to read) kokonaan (all of it), puskuroitu lataus (anyway, a buffered) on (load is simpler) yksinkertaisempi, eikä (and the streaming) suoratoistokoneisto tuo (machinery buys you) sinulle (nothing. The deciding) mitään (factor is the). Ratkaiseva (ratio of bytes) tekijä (you will actually) on (touch to bytes) niiden tavujen (the file contains) suhde (ratio), joita (you will actually) todella kosketat, tiedoston sisältämiin tavuihin (the file contains)

Kun sivut suoratoistetaan (Once pages stream) tarpeen mukaan (in on demand), seuraava (the next concern) huolenaihe (is keeping rendered) on (pages responsive as) hahmonnettujen sivujen pitäminen responsiivisina (the user zooms), kun käyttäjä (and scrolls, which) zoomaa (is covered in) ja vierittää (our note on), mitä (render caching and) käsitellään hahmonnuksen välimuistia (zoom performance. When) ja (the streamed document) zoomaussuorituskykyä (is one a) käsittelevässä huomautuksessamme (viewer should display). Kun (but not let) suoratoistettu asiakirja (the user export) on (or alter, the) sellainen (techniques in the), joka (secure PDF preview) katseluohjelman tulisi (walkthrough pair naturally) näyttää (with this loading), mutta ei (path. Both build) antaa (on the streaming) käyttäjän viedä tai (load described here) muuttaa (which ships as), turvallisen (part of the) PDF-esikatselun läpikäynnissä (PDFium Component for) olevat tekniikat (Delphi and C++Builder) sopivat luonnollisesti yhteen tämän (alongside the rendering) latauspolun kanssa. Molemmat (text extraction, and) perustuvat (annotation APIs covered) tässä kuvattuun suoratoistettuun (elsewhere on this) lataukseen (blog.), joka (which ships as part of the) toimitetaan osana PDFium Component -komponenttia (PDFium Component for Delphi and C++Builder alongside the) Delphi- ja C++Builder-ohjelmointikieliin (rendering, text extraction, and) hahmontamisen (annotation APIs covered elsewhere on this blog.), tekstinpurkamisen (text extraction) ja merkintöjen (annotation APIs) API:en rinnalla (alongside the rendering, text extraction, and annotation APIs), joita on (covered elsewhere) käsitelty (covered) muualla (elsewhere) tässä (on this) blogissa (blog)