Maak thuis Uw eigen e-book in Uw vrije tijd zonder schroeven

Door Pieter Masereeuw

Dat e-books populair zijn, zal niemand ontgaan zijn. In dit artikel kunt u lezen hoe een e-bookbestand in elkaar zit, meer precies gezegd: hoe een ePub-bestand in elkaar zit. Het ePub-formaat is in ons land het gangbare formaat voor e-books, zeker sinds eind 2009 toen Bol.com en Sony onze markt bestormden.

Natuurlijk kunt u op zoek gaan naar een tool om uw bestanden naar ePub te converteren. De zoekmachine zal u daarbij graag op weg helpen. In dit artikel laten we echter zien hoe je zelf XML-bestanden naar ePub kunt converteren. De nadruk ligt hierbij op zelf, vandaar de op het bijna gelijknamige bulkboek van Drs. P. gebaseerde titel.

Dit artikel is een voortzetting van het overzichtsartikel over ePub in de <!ELEMENT van oktober 2009.

Een ePub-versie van deze tekst kunt u vinden op www.masereeuw.nl/epub-maken.epub.

Voor we beginnen

In bladen voor een algemeen publiek is het gebruikelijk om allerlei verontschuldigingen te uiten zodra een tekst technisch wordt. Dat doen we hier niet. Deze tekst gaat over techniek. Geniet ervan, of zap snel door naar een ander artikel.

Technische teksten bevatten soms een overdaad aan leukigheid om de taaie kost pruimbaar te maken. Notoir dieptepunt zijn daarbij de boeken over Perl. Perl is (misschien wel daardoor) nooit mijn favoriete taal geworden, maar de gortdroge boeken van Michael Kay reken ik tot mijn dierbaarste bezit. Verwacht dus geen grapjes. Trouwens: bij Michael Kay is tussen de regels door genoeg kwaliteitshumor te vinden. Hier niet.

In dit artikel converteren we een XML-bestand naar een ePub-bestand. Invoerbestand is een aflevering van <!ELEMENT, geschreven conform de <!ELEMENT-DTD. Dit is een beperkte versie van de DocBook-DTD. We doen de conversie met XSLT 2.0.

Wat zit er in een ePub-bestand

Een ePub-bestand is een ZIP-bestand dat volgens een aantal eenvoudige, maar specifieke regels is samengesteld. Globaal gesproken bevat het twee soorten onderdelen:

Beide soorten onderdelen komen in dit artikel aan bod.

Alle onderdelen zitten in een mapstructuur. Bij het samenstellen van deze structuur heb je de volledige vrijheid, afgezien van het volgende:

Deze structuur is vastgelegd in het OEBPS container format (OCF), opgenomen binnen de ePub-standaard (OEBPS is een afkorting van Open eBook Publication Structure). Aan het eind van dit artikel, als alle onderdelen genoemd zijn, laat ik een voorbeeld zien van de mapstructuur.

De “echte” inhoud

Tekst

De tekst van een ePub-publicatie is meestal gebaseerd op XHTML plus CSS. Eigenlijk wordt aangeraden om geen XHTML te gebruiken, maar de DTBook-DTD. DTBook – Digital Talking Book – lijkt wel wat op XHTML, maar heeft meer boekachtige voorzieningen, zoals paginanummers, noten, frontmatter, backmatter, kolommen, sidebars en een beter afgedwongen documentstructuur. Tevens bevat DTBook SMIL-uitbreidingen voor geluid en spraak – al dan niet synthetisch.

Ik heb wel eens met DTBook geëxperimenteerd, maar de praktijk viel mij tegen – zo hoopte ik op een ingebouwde rendering van boekachtige element als sidebars, maar dat effect was afwezig totdat je zelf met CSS aan de gang ging. Ook kolommen werkten niet out-of-the-box.

Geheel in strijd met mijn calvinistische opvoeding bewandel ik voorlopig maar de brede weg van XHTML. Lekker vertrouwd, en ook makkelijk om je conversie uit te testen in een browser.

Wat dat laatste betreft: testen in de browser lijkt aardig, maar kan tevens een valkuil blijken te zijn. Tijdens het werken kwam ik er op een gegeven moment achter dat de position-properties van CSS niet ondersteund worden in ePub. En daar ging mijn oplossing voor margeteksten. Test daarom, bij het ontwikkelen, regelmatig de stand van zaken in een programma als Adobe Digital Editions en, beter nog, op een aantal echte e-readers.

In de <!ELEMENT-DTD is <article> de container voor artikelen. In ons voorbeeld maken we van elk artikel een los XHTML-bestand om later in de ePub-file op te nemen. Ook in de praktijk zul je bij je conversie meerdere XHTML-bestanden maken, niet alleen vanwege de beheersbaarheid, maar ook omdat sommige e-readers een groottebeperking hebben – 300KB voor een XHTML-file (tekst + tags, maar de afbeeldingen waarnaar verwezen wordt, tellen niet mee). 300KB is weinig – je bent er snel, zeker als je document tabellen bevat.

Omdat een aflevering van ons mooie tijdschrift tot nu toe gelukkig nog steeds meerdere artikelen bevat, kunnen we in XSLT de constructie zoals getoond in afbeelding 1 toepassen om losse XHTML-files te maken. De namen van die bestanden zijn belangrijk – we moeten later in het TOC-bestand dezelfde namen gebruiken. Het XSLT-fragment in deze afbeelding laat zien hoe je losse bestanden aanmaakt. De functie die de naam berekent, plaats je in de praktijk in een losse file die je in diverse andere stylesheets importeert om de naamgeving van bestanden te centraliseren.

afb1.png Afbeelding 1: het maken van losse XHTML-bestanden

Merk op dat we de XHTML-bestanden plaatsen in de map OEBPS/tekst en dat er verwezen wordt naar een CSS-bestand in de map OEBPS/css. Deze mappen vormen samen een deel van de directorystructuur die we uiteindelijk zullen gaan zippen om een ePub-bestand te maken.

De rest van de XML-naar-XHTML-conversie, zoals getriggerd door de aanroep van <xsl:apply-templates/> is, zoals dat zo mooi heet, “left as an exercise”.

Noten

Een speciaal probleem vormen voetnoten. Bij de oplossing die mij uiteindelijk het meest bevalt, plaats ik alle voetnoten samen in een apart bestand en genereer ik heen- en weerverwijzingen. Daarbij ga ik er wel vanuit dat de lezer beschikt over een reader die het volgen van hyperlinks ondersteunt.

Een andere oplossing bestaat eruit dat je een noot onderaan een tekstsectie of zelfs de XHTML-pagina plaatst. Dat werkt goed; ik heb deze oplossing uiteindelijk toch verworpen omdat in de gegeven situatie de XHTML-bestanden groter dan 300KB werden, waardoor deze – met de hand, helaas – moesten worden opgeknipt. En bij dat opknippen was het nogal bewerkelijk om de verwijzingen correct te houden.

Nog een andere oplossing bestaat eruit dat je voor elke noot een los bestandje genereert. Die oplossing heeft als nadeel dat bij de e-readers die ik ken, de bladervolgorde niet goed werkt: als de gebruiker een voetnoot leest en op de knop voor de volgende pagina drukt, wordt de volgende voetnoot vertoond, en die kan heel ergens anders in de hoofdtekst worden aangeroepen. Eigenlijk zou je willen dat er bij een voetnoot geen “volgende pagina” is na het einde van de noot; de software zou moeten reageren door een boze piep te laten horen of door gewoon niets te doen. De ePub-standaard voorziet ook in iets dergelijks – je kunt in de metadata-file in de verwijzing naar zo'n bestandje (element <itemref>) het attribuut linear="no" opnemen, maar ik heb geen verschil in verwerking waar kunnen nemen wanneer dit attribuut wel of niet aanwezig was.

Afbeeldingen

Het verwerken van afbeeldingen is niet heel erg moeilijk, maar vereist wat zorgvuldigheid. Om te beginnen moeten alle afbeeldingen worden gekopieerd naar de map-hiërarchie die we uiteindelijk gaan zippen. Daarbij moet je ervoor zorgen dat je geen afbeeldingen kopieert die niet gebruikt worden (vanwege de omvang van het bestand, en om foutmeldingen bij controle te vermijden). Verder is het niet nodig om hogeresolutie-afbeeldingen te gebruiken.

De scripts die ik zelf gebruik, verzamelen de afbeeldingen uit de gegenereerde XHTML-files. Vervolgens worden ze ontdubbeld en uiteindelijk gekopieerd. Het is uiteraard net zo goed mogelijk om vanuit het bron-XML te werken – de keuze is min of meer arbitrair.

Terzijde: afbeeldingen zijn problematische onderdelen in een e-book, zeker in het geval van echte e-readers met e-paper: kleuren en contrasten vallen weg en er zijn altijd problemen met de maat.

Fonts

In principe kun je je eigen fonts opnemen in je ePub-bestand. Het is de vraag of je dat wilt. Als je tekst nogal wat buitenissige tekens bevat, kan het onvermijdelijk zijn om een font bij te sluiten dat die tekens bevat. In andere gevallen moet je je afvragen wat de meerwaarde is van je eigen fonts: het bestand wordt immers omvangrijker (zeker als je voor vette en cursieve varianten aparte fonts moet insluiten), er spelen copyrightkwesties en de leesbaarheid van het font op e-paper kan een issue zijn.

Feit is wel dat het kan. Indien je besluit om fonts op te nemen, kopieer je deze naar een map in de directorystructuur; verder maak je ze bekend door ze in de CSS-file te noemen, zoals in het voorbeeld in afbeelding 2:

afb2.png Afbeelding 2: CSS-fragment met font-declaratie

Tevens zul je de fonts moeten noemen in de metadata-file. Het mimetype van Truetype-fonts is font/truetype.

Administratieve bestanden

Naast de inhoudelijke bestanden bevat een ePub-bestand een aantal administratieve bestanden:

Het metadatabestand

Het metadatabestand ( metadata.opf, maar de naam is vrij) is het bestand waarnaar de startfile van het ePub-bestand ( container.xml) verwijst. Het bestaat uit drie hoofdonderdelen:

Afbeelding 3 geeft een voorbeeld van de hoofdstructuur van het metadata-bestand:

afb3.png Afbeelding 3: de hoofdstructuur van het bestand metadata.opf

In afbeelding 4 zien we hoe je met XSLT de manifest- en spinesecties genereert:

afb4.png Afbeelding 4: de manifest- en spinesecties van metadata.opf

Uit afbeelding 4 valt niet af te lezen hoe je de afbeeldingen opneemt. Daar zijn verschillende mogelijkheden voor. Ik heb ervoor gekozen om de afbeeldingen buiten XSLT te ontdubbelen; de ontdubbelde afbeeldingsnamen worden in een tijdelijk XML-bestandje geplaatst. Dat XML-bestandje wordt ingelezen met behulp van de document()-functie van XPath. In principe kun je het ontdubbelen ook in XSLT doen, maar omdat ik in mijn scripts ook nog bestanden moest kopiëren, was de genoemde oplossing het makkelijkst.

Lastig bij afbeeldingen is dat je het mime-type moet meegeven. Om die te bepalen gebruik ik de bestandsextensie, een voor de hand liggende oplossing, die wat geknutsel met reguliere expressies vereist. Zie afbeelding 5. Verondersteld is dat de code uit afbeelding 5 wordt ingevoegd na de eerste <xsl:for-each> van afbeelding 4. Opname van de afbeeldingen in de spine is niet nodig als er vanuit de XHTML-files naar wordt verwezen.

afb5.png Afbeelding 5: afbeeldingen opnemen in metadata.opf

Het TOC-bestand

Het TOC-bestand ( toc.ncx, maar de naam is vrij) bestaat uit drie verplichte delen: de <head>, de <docTitle> en de <navMap>. Op de <navMap> kun je nog een aantal <navList>-elementen laten volgen. Die elementen vormen de reeds eerder genoemde “guided tours”, maar omdat die noch in de belangrijkste e-readers, noch in Adobe Digitals Editions ondersteund worden, gaan we daar niet verder op in.

Afbeelding 6 toont de eerste twee delen van het TOC-bestand.

afb6.png Afbeelding 6: de eerste twee secties van bestand toc.ncx

Het interessantste deel van het TOC-bestand is natuurlijk de <navMap>-sectie. Zoals uit afbeelding 7 blijkt, is dit een geneste structuur die gevormd wordt door <navPoint>-elementen. Elk navigatiepunt bevat een label met tekst die in de inhoudsopgave van het programma van de e-reader vertoond wordt (zorg voor een goede behandeling van whitespace!).

Ieder <navPoint>-element heeft een uniek id. Dit id is een hulpmiddel voor software die verwijzingen naar navigatiepunten wil vasthouden, zoals bookmarks. Verder heeft elk <navPoint>-element een verplicht playOrder-attribuut. Dit attribuut bevat het volgnummer van elk navigatiepunt. Er mogen geen nummers ontbreken en de nummers moeten in de juiste volgorde staan. Het playOrder-attribuut is volgens mij op dit moment weliswaar verplicht, maar nog niet nuttig. Het is van belang op het moment dat je van de functionaliteiten van <navLists>'s gebruik kunt maken: op dat moment moet de e-readersoftware zonder al te veel ingewikkelde processing kunnen beslissen op welke plaats de lezer zich in het document bevindt, als hij of zij besluit om de guided tour te verlaten.

De TOC van afbeelding 7 wordt in XSLT samengesteld door alle artikelen en (geneste) secties te selecteren. De secties worden voorzien van een id (in de tekst) en een id-ref (in de TOC). De waarde van het playorder attribuut komt tot stand door middel van <xsl:number count="article | section" level="any"/> ( level=”any” is hier cruciaal).

De echte verwijzing naar de content vindt plaats met het <content src=”...”/>, waarbij de waarde van src een hyperlink is die eventueel van een anker (hekje gevolgd door id) is voorzien.

Afbeelding 7 zal een boel duidelijk maken.

afb7.png Afbeelding 7: geneste navigatiepunten in bestand toc.ncx

Voor de hier verder niet behandelde <navList>-elementen geldt overigens dat ze geen nesting kennen, en dat de playOrder-attributen niet aaneensluitend hoeven te zijn.

De resterende lijm: mimetype en META-INF/container.xml.

Onze directorystructuur ziet er nu uit als in afbeelding 8:

afb8.png Afbeelding 8: de mapstructuur van een ePub-bestand

De file mimetype hebben we hierboven al genoemd – deze bevat alleen maar de tekst application/epub+zip. De file META-INF/container.xml is niet spannend, maar wel belangrijk. Dit bestand vertelt de e-reader waar de OPF-file staat. Zoals u in afbeelding 9 kunt zien, bevat deze file een element <rootfiles> en een element <rootfile>. Dit suggereert dat er meerdere rootfiles kunnen zijn en dat is ook zo: container.xml kan naar naar meerdere plekken verwijzen – naast een OPF-file zou je bijvoorbeeld ook een PDF-file kunnen noemen, om als alternatief medium te dienen. Ik heb dit overigens nooit in de praktijk getest, dus ik weet niet in hoeverre de verschillende leesapplicaties dit ondersteunen.

afb9.png Afbeelding 9: de file container.xml

Het aanmaken van het ZIP-bestand

Het zip-bestand dient niet de gebruikelijke extensie .zip te hebben, maar .epub. Je maakt het bestand niet met een standaard compressietool, want de volgorde is belangrijk: de file mimetype moet aan het begin staan en mag niet worden gecomprimeerd.

Met behulp van het zip-commando dat met veel Linux-distributies wordt meegeleverd (en dat vermoedelijk ook wel elders beschikbaar is), kun je op de volgende manier een ePub-bestand aanmaken (verondersteld is dat de huidige directory de root is van de ePub-map):

EPUBFILE=../element.epub

zip -q "$EPUBFILE" -X -D -0 mimetype

zip -q -g "$EPUBFILE" -X -D -r -9 META-INF OEBPS

Voor meer details verwijs ik naar de man-page.

De epub-checker

Om na te gaan of het ePub-bestand aan de normen voldoet, kun je gebruik maken van de ePub-checker die te vinden is bij Google Code. Dit tool wordt in de verwijzingen hieronder genoemd. Helaas doet het tool geen controle van de CSS-file.

Verwijzingen

Pieter Masereeuw is zelfstandig uitgeeftechnoloog, als dat tenminste een woord is. En anders is hij het ook. Hij houdt zich graag bezig met Open Source-oplos­singen, XML, Java en XSLT.