Minulý týden při programování Rozepiš.cz jsem narazil na zajímavou programovací výzvu. Potřeboval jsem pro uživatele vytvořit exportování knih, které jsem měl uložené jako html do jiných formátů, kdyby si je chtěli přečíst například v e-booku. Konkrétně jsem začal na docx, pdf, epub a mobi.

Člověk si řekne, vždyť to není nic nereálného, stáhnu knihovnu, vezmu html, hodím ho do knihovny a vyplivne mi to výsledek. Jenže najít správnou knihovnu, která funguje s ASP.NET Core, která vám převede HTML tak jak vy potřebujete a která vyplivne výsledek který se bude dát přečíst není tak úplně jednoduché, obzvlášť pokud po té knihovně ještě chcete aby byla zdarma že.

Samozřejmě existuje spoustu knihoven, které tyto exporty dovedou, jsou i takové, které zvládnou všechny čtyři exporty najednou…pokud jste ochodní za ně dát 200$+…ročně. Pro mě jako pro individuálního vývojáře, který navíc ještě studuje jsou tyto částky trochu přes čáru a tak jsem začal hledat nějakou open source možnost, která by mi vyhovovala. Po dni hledání, programování atd. jsem to konečně našel a tady vám představím svůj výsledek.

DOCX

Prvním typem souborů na které jsem se zaměřil byly soubory docx, musím říct že nebylo ani tak složité nalézt knihovnu, která by tento převod uměla, ovšem nalézt knihovnu, která by tento převod uměla a nepotřebovala k tomu nainstalovaný word (což na svém linuxovém serveru opravdu nemám) bylo malinko složitější. Nakonec však nečekaně podal pomocnou ruku tým, který dělá také knihovny které pracují s wordem a jejich open source knihovna DocumentFormat.OpenXml kterou lze najít jako NuGet balíček k mým potřebám naprosto stačila. Potom už stačilo jen pár řádku kódu ve kterém se vytvoří nový wordový dokument a vloží se do něj obsah, ve kterém se mu řekne, že ten obsah je html, výsledek kód pak vidíte níže.

using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
 
private void ExportWord(string htmlContent)
{
    var filename = "file_to_save.docx";
 
    // Vytvoříme nový word dokument který se uloží na místo dle zadaného filename
    var document = WordprocessingDocument.Create(filename, WordprocessingDocumentType.Document);
    var mainDocumentPart = document.AddMainDocumentPart();
 
    // Načteme html obsah jako byte stream a nakrmíme jí mainDocumentPart, kterou vytvoříme
    var stream = new MemoryStream(Encoding.UTF8.GetBytes(htmlContent));
    var htmlSectionId = "book";
    var formatImportPart =
        mainDocumentPart.AddAlternativeFormatImportPart(AlternativeFormatImportPartType.Html, htmlSectionId);
    formatImportPart.FeedData(stream);
 
    var altChunk = new AltChunk {Id = htmlSectionId};
 
    // Vložíme mainPart do těla dokumentu
    mainDocumentPart.Document = new Document();
    var sectionProps = new SectionProperties();
    mainDocumentPart.Document.Body = new Body(altChunk, sectionProps);
 
    // Dokument uložíme a zavřeme
    document.MainDocumentPart.Document.Save();
    document.Dispose();
}

PDF

Narozdíl od exportování do docx jsem nad exportem do pdf strávil opravdu hodně času. Celou dobu jsem se totiž snažil najít knihovnu, která by dokázala převést html do pdf a nebyla závislá na nějaké externí knihovně. Toto hledání mi zabralo spoustu času a nakonec z toho stejně nic nebylo protože všechny knihovny které by to uměly byly drahé a pro mě tedy nepoužitelné.

Z toho důvodu jsem nakonec došel k závěru, že proč hledat něco, co už stejně někdo napsal a proč tedy nepoužít nějakou externí knihovnu. Nakonec jsem tedy skončil u knihovny wkhtmltox. Tato knihovna se používá pro převod html do pdf nebo obrázků a C# pro ní má na NuGetu docela dost wrapperů.

Nakonec mi padl do oka wrapper DinkToPdf, který právě používá zmíněnou knihovnu a dá se také hezky použít. Co po vás chce je, abyste si stáhli knihovnu libwkhtmltox, podle os buď pro mac, linux nebo windows a vložili ji do složky s projektem. Wrapper poté tuto knihovnu spustí a použije ji k exportu html do pdf. Všechna nastavení která můžete pro knihovnu použít lze v DinkToPdf nastavit a výsledné pdf vypadá také velice hezky. Zde je příklad.

using DinkToPdf;
 
private void ExportPDF(string htmlContent)
{
    var filename = "file_to_save.pdf";
    // Vytvoříme pdf converter
    var converter = new SynchronizedConverter(new PdfTools());
 
    // A dokument, který budeme vytvářet, včetně různých nastavení
    var document = new HtmlToPdfDocument
    {
        GlobalSettings =
        {
            ColorMode = ColorMode.Color,
            Orientation = Orientation.Portrait,
            PaperSize = PaperKind.A5,
            // Zde nastavuji soubor, do kterého se html zkonvertuje a uloží, je možné jej konvertovat i do proměnné
            Out = filename,
            // DPI je nastaveno na 300 protože defaultně ho má wkhtmltox nastaveno na něco jako 96 a pdf se pak nedá přečíst protože písmena jsou moc malá
            DPI = 300
        },
        Objects =
        {
            new ObjectSettings
            {
                PagesCount = true,
                HtmlContent = htmlContent,
                WebSettings =
                {
                    DefaultEncoding = "utf-8"
                },
                HeaderSettings =
                {
                    FontSize = 9
                }
            }
        }
    };
    converter.Convert(document);
}

Tady bych se rád pozastavil ještě nad jednou věcí, když jsem totiž aplikaci vydával, zjistil jsem, že docker mi vyhazuje chybu protože nemůže najít libwkhtmltox knihovnu nebo nějakou jinou, kterou ona potřebuje. S tím mi velice pomohl tenhle článek.

Ve zkratce: problém byl v tom, že starší verze wkhtmltox neumějí pracovat bez podpory obrazovky a na linuxu potřebují další knihovny, se kterými fungují. První problém byl vyřešen stažením nové verze knihovny..konkrétně verze 0.12.4 nebo vyšší a druhý problém byl vyřešen instalací potřebných knihoven do linuxu, konkrétněji, přidáním tohoto scriptu do Dockerfile.

apt-get update && \
    apt-get install -y --no-install-recommends zlib1g fontconfig libfreetype6 libx11-6 libxext6 libxrender1 && \
    rm -rf /var/lib/apt/lists/*

Celý dockerfile včetně sestavení aplikace poté vypadá takto:

FROM microsoft/aspnetcore-build AS builder
WORKDIR /source
COPY *.csproj .
RUN dotnet restore

COPY . .
RUN dotnet publish --output /app/ --configuration Release

FROM microsoft/aspnetcore
RUN apt-get update && \
        apt-get install -y --no-install-recommends zlib1g fontconfig libfreetype6 libx11-6 libxext6 libxrender1 && \
        rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY --from=builder /app .
COPY libwkhtmltox.so .
COPY kindlegen .
ENTRYPOINT ["dotnet", "Rozepis.dll"]

EPUB

Třetím typem souboru, do kterého jsem html převáděl byl typ pro čtečky elektronických knih epub. Poté co jsem se několik hodin mořil s PDF než jsem přišel na to jak to udělat co nejjednodušeji musím říct, že epub pro mě byla docela oddechovka.  Knihovnu EPubFactory bylo radost použít a jediné s čím byl trochu problém bylo uvědomit si, že EPub je vlastně zip, ve kterém se každá kapitola nachází ve vlastním html souboru a je to vlastně seznam html souborů, spojený nějakým souborem s definicí, tyto soubory nakonec dávají dohromady knihu. Po zjištění, že každý soubor (každá kapitola) musí mít vlastní html definici, včetně hlavičky a těla již nebylo nic složitého tyto soubory vytvořit. Níže přikládám zdrojový kód.

using EPubFactory;
 
private async Task ExportEpub(Book book, User user, List chapters)
{
    var filename = $"file_to_save.epub";
 
    // Vytvoříme soubor pro knihu a vezmeme si jeho stream
    var bookStream = File.Create(filename);
 
    // Vytvoříme epub writer kterému dáme soubor, název knihy, jméno autora a identifikátor
    using (var writer = await EPubWriter.CreateWriterAsync(bookStream, book.Name, user.Username, book.Id.ToString()))
    {
        // Můžeme přidat metadata
        writer.Publisher = "Rozepiš.cz";
 
        //Dále už jen přidáváme kapitoly se jménem souboru v archivu, jménem kapitoly a jejím obsahem jako html
        foreach (var chapter in chapters)
        {
            await writer.AddChapterAsync(chapter.Id + ".html", chapter.Name, chapter.Content);
        }
        // Nakonec zapíšeme konec archivu a ukončíme zapisování
        await writer.WriteEndOfPackageAsync();
    }
}

MOBI

Posledním z exportovaných typů mi zůstal typ pro čtečky Amazon Kindle a to typ mobi. Po zkušenostech s hledáním knihoven na PDF jsem si řekl že tu první zdlouhavou vyhledávací část přeskočím a rovnou přejdu k řešení, které je již vytvořené a funguje. Tímto řešením se pro mě stal program kindlegen, což je utilitka od amazonu fungující jako konvertor html, epub atd. právě do formátu mobi. A vzhledem k tomu, že epub jsem předtím již jednou vytvořil, proč ho nevyužít znovu, Stáhl jsem si tedy kindlegen a dal ho do složky řešení, stejně jako předtím wkhtmltox knihovnu, pro převod jsem pak použil následující kód.

private async Task ExportMobi(Book book, User user, List chapters)
{
    await ExportEpub(book, user, chapters);
    var process = Process.Start("kindlegen", "file_to_save.epub");
    process.WaitForExit();
    File.Delete(epubFile);
}

Prakticky jsem pouze spustil kindlegen, který mi z dříve vytvořeného epub souboru vytvořil soubor mobi a následně jsem epub smazal.

 

Doufám, že vám tenhle článek k něčemu bude, pokud byste měli nějaké nápady na vylepšení nebo připomínky, nechte mi je prosím v komentářích. Díky.

Napsat komentář

Vaše emailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *