"Fossies" - the Fresh Open Source Software Archive

Member "poppler-0.82.0/utils/HtmlOutputDev.cc" (25 Oct 2019, 55038 Bytes) of package /linux/misc/poppler-0.82.0.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "HtmlOutputDev.cc" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 0.81.0_vs_0.82.0.

    1 //========================================================================
    2 //
    3 // HtmlOutputDev.cc
    4 //
    5 // Copyright 1997-2002 Glyph & Cog, LLC
    6 //
    7 // Changed 1999-2000 by G.Ovtcharov
    8 //
    9 // Changed 2002 by Mikhail Kruk
   10 //
   11 //========================================================================
   12 
   13 //========================================================================
   14 //
   15 // Modified under the Poppler project - http://poppler.freedesktop.org
   16 //
   17 // All changes made under the Poppler project to this file are licensed
   18 // under GPL version 2 or later
   19 //
   20 // Copyright (C) 2005-2013, 2016-2019 Albert Astals Cid <aacid@kde.org>
   21 // Copyright (C) 2008 Kjartan Maraas <kmaraas@gnome.org>
   22 // Copyright (C) 2008 Boris Toloknov <tlknv@yandex.ru>
   23 // Copyright (C) 2008 Haruyuki Kawabe <Haruyuki.Kawabe@unisys.co.jp>
   24 // Copyright (C) 2008 Tomas Are Haavet <tomasare@gmail.com>
   25 // Copyright (C) 2009 Warren Toomey <wkt@tuhs.org>
   26 // Copyright (C) 2009, 2011 Carlos Garcia Campos <carlosgc@gnome.org>
   27 // Copyright (C) 2009 Reece Dunn <msclrhd@gmail.com>
   28 // Copyright (C) 2010, 2012, 2013 Adrian Johnson <ajohnson@redneon.com>
   29 // Copyright (C) 2010 Hib Eris <hib@hiberis.nl>
   30 // Copyright (C) 2010 OSSD CDAC Mumbai by Leena Chourey (leenac@cdacmumbai.in) and Onkar Potdar (onkar@cdacmumbai.in)
   31 // Copyright (C) 2011 Joshua Richardson <jric@chegg.com>
   32 // Copyright (C) 2011 Stephen Reichling <sreichling@chegg.com>
   33 // Copyright (C) 2011, 2012 Igor Slepchin <igor.slepchin@gmail.com>
   34 // Copyright (C) 2012 Ihar Filipau <thephilips@gmail.com>
   35 // Copyright (C) 2012 Gerald Schmidt <solahcin@gmail.com>
   36 // Copyright (C) 2012 Pino Toscano <pino@kde.org>
   37 // Copyright (C) 2013 Thomas Freitag <Thomas.Freitag@alfa.de>
   38 // Copyright (C) 2013 Julien Nabet <serval2412@yahoo.fr>
   39 // Copyright (C) 2013 Johannes Brandstätter <jbrandstaetter@gmail.com>
   40 // Copyright (C) 2014 Fabio D'Urso <fabiodurso@hotmail.it>
   41 // Copyright (C) 2016 Vincent Le Garrec <legarrec.vincent@gmail.com>
   42 // Copyright (C) 2017 Caolán McNamara <caolanm@redhat.com>
   43 // Copyright (C) 2018 Klarälvdalens Datakonsult AB, a KDAB Group company, <info@kdab.com>. Work sponsored by the LiMux project of the city of Munich
   44 // Copyright (C) 2018 Thibaut Brard <thibaut.brard@gmail.com>
   45 // Copyright (C) 2018, 2019 Adam Reichold <adam.reichold@t-online.de>
   46 // Copyright (C) 2019 Oliver Sander <oliver.sander@tu-dresden.de>
   47 //
   48 // To see a description of the changes please see the Changelog file that
   49 // came with your tarball or type make ChangeLog if you are building from git
   50 //
   51 //========================================================================
   52 
   53 #include "config.h"
   54 #include <stdio.h>
   55 #include <stdlib.h>
   56 #include <stdarg.h>
   57 #include <stddef.h>
   58 #include <ctype.h>
   59 #include <math.h>
   60 #include <iostream>
   61 #include "goo/GooString.h"
   62 #include "goo/gbasename.h"
   63 #include "goo/gbase64.h"
   64 #include "goo/gbasename.h"
   65 #include "UnicodeMap.h"
   66 #include "goo/gmem.h"
   67 #include "Error.h"
   68 #include "GfxState.h"
   69 #include "Page.h"
   70 #include "Annot.h"
   71 #include "PNGWriter.h"
   72 #include "GlobalParams.h"
   73 #include "HtmlOutputDev.h"
   74 #include "HtmlFonts.h"
   75 #include "HtmlUtils.h"
   76 #include "InMemoryFile.h"
   77 #include "Outline.h"
   78 #include "PDFDoc.h"
   79 
   80 #ifdef ENABLE_LIBPNG
   81 #include <png.h>
   82 #endif
   83 
   84 #define DEBUG __FILE__ << ": " << __LINE__ << ": DEBUG: "
   85 
   86 class HtmlImage
   87 {
   88 public:
   89     HtmlImage(GooString *_fName, GfxState *state)
   90       : fName(_fName) {
   91     state->transform(0, 0, &xMin, &yMax);
   92     state->transform(1, 1, &xMax, &yMin);
   93   }
   94  ~HtmlImage() { delete fName; }
   95   HtmlImage(const HtmlImage &) = delete;
   96   HtmlImage& operator=(const HtmlImage &) = delete;
   97 
   98   double xMin, xMax;        // image x coordinates
   99   double yMin, yMax;        // image y coordinates
  100   GooString  *fName;        // image file name
  101 };
  102 
  103 // returns true if x is closer to y than x is to z
  104 static inline bool IS_CLOSER(float x, float y, float z) { return fabs((x)-(y)) < fabs((x)-(z)); }
  105 
  106 extern bool complexMode;
  107 extern bool singleHtml;
  108 extern bool dataUrls;
  109 extern bool ignore;
  110 extern bool printCommands;
  111 extern bool printHtml;
  112 extern bool noframes;
  113 extern bool stout;
  114 extern bool xml;
  115 extern bool noRoundedCoordinates;
  116 extern bool showHidden;
  117 extern bool noMerge;
  118 
  119 extern double wordBreakThreshold;
  120 
  121 static bool debug = false;
  122 static GooString *gstr_buff0 = nullptr; // a workspace in which I format strings
  123 
  124 #if 0
  125 static GooString* Dirname(GooString* str){
  126   
  127   char *p=str->c_str();
  128   int len=str->getLength();
  129   for (int i=len-1;i>=0;i--)
  130     if (*(p+i)==SLASH) 
  131       return new GooString(p,i+1);
  132   return new GooString();
  133 } 
  134 #endif
  135 
  136 static const char *print_matrix(const double *mat) {
  137   delete gstr_buff0;
  138 
  139   gstr_buff0 =  GooString::format("[{0:g} {1:g} {2:g} {3:g} {4:g} {5:g}]",
  140                                   *mat, mat[1], mat[2], mat[3], mat[4], mat[5]);
  141   return gstr_buff0->c_str();
  142 }
  143 
  144 static const char *print_uni_str(const Unicode *u, const unsigned uLen) {
  145   GooString *gstr_buff1 = nullptr;
  146 
  147   delete gstr_buff0;
  148 
  149   if (!uLen) return "";
  150   gstr_buff0 = GooString::format("{0:c}", (*u < 0x7F ? *u & 0xFF : '?'));
  151   for (unsigned i = 1; i < uLen; i++) {
  152     if (u[i] < 0x7F) {
  153       gstr_buff1 = gstr_buff0->append(u[i] < 0x7F ? static_cast<char>(u[i]) & 0xFF : '?');
  154       delete gstr_buff0;
  155       gstr_buff0 = gstr_buff1;
  156     }
  157   }
  158 
  159   return gstr_buff0->c_str();
  160 }
  161 
  162 //------------------------------------------------------------------------
  163 // HtmlString
  164 //------------------------------------------------------------------------
  165 
  166 HtmlString::HtmlString(GfxState *state, double fontSize, HtmlFontAccu* _fonts) : fonts(_fonts) {
  167   GfxFont *font;
  168   double x, y;
  169 
  170   state->transform(state->getCurX(), state->getCurY(), &x, &y);
  171   if ((font = state->getFont())) {
  172     double ascent = font->getAscent();
  173     double descent = font->getDescent();
  174     if( ascent > 1.05 ){
  175         //printf( "ascent=%.15g is too high, descent=%.15g\n", ascent, descent );
  176         ascent = 1.05;
  177     }
  178     if( descent < -0.4 ){
  179         //printf( "descent %.15g is too low, ascent=%.15g\n", descent, ascent );
  180         descent = -0.4;
  181     }
  182     yMin = y - ascent * fontSize;
  183     yMax = y - descent * fontSize;
  184     GfxRGB rgb;
  185     state->getFillRGB(&rgb);
  186     HtmlFont hfont=HtmlFont(font, static_cast<int>(fontSize-1), rgb);
  187     if (isMatRotOrSkew(state->getTextMat())) {
  188       double normalizedMatrix[4];
  189       memcpy(normalizedMatrix, state->getTextMat(), sizeof(normalizedMatrix));
  190       // browser rotates the opposite way
  191       // so flip the sign of the angle -> sin() components change sign
  192       if (debug)
  193         std::cerr << DEBUG << "before transform: " << print_matrix(normalizedMatrix) << std::endl;
  194       normalizedMatrix[1] *= -1;
  195       normalizedMatrix[2] *= -1;
  196       if (debug)
  197         std::cerr << DEBUG << "after reflecting angle: " << print_matrix(normalizedMatrix) << std::endl;
  198       normalizeRotMat(normalizedMatrix);
  199       if (debug)
  200         std::cerr << DEBUG << "after norm: " << print_matrix(normalizedMatrix) << std::endl;
  201       hfont.setRotMat(normalizedMatrix);
  202     }
  203     fontpos = fonts->AddFont(hfont);
  204   } else {
  205     // this means that the PDF file draws text without a current font,
  206     // which should never happen
  207     yMin = y - 0.95 * fontSize;
  208     yMax = y + 0.35 * fontSize;
  209     fontpos=0;
  210   }
  211   if (yMin == yMax) {
  212     // this is a sanity check for a case that shouldn't happen -- but
  213     // if it does happen, we want to avoid dividing by zero later
  214     yMin = y;
  215     yMax = y + 1;
  216   }
  217   col = 0;
  218   text = nullptr;
  219   xRight = nullptr;
  220   link = nullptr;
  221   len = size = 0;
  222   yxNext = nullptr;
  223   xyNext = nullptr;
  224   htext=new GooString();
  225   dir = textDirUnknown;
  226 }
  227 
  228 
  229 HtmlString::~HtmlString() {
  230   gfree(text);
  231   delete htext;
  232   gfree(xRight);
  233 }
  234 
  235 void HtmlString::addChar(GfxState *state, double x, double y,
  236              double dx, double dy, Unicode u) {
  237   if (dir == textDirUnknown) {
  238     //dir = UnicodeMap::getDirection(u);
  239     dir = textDirLeftRight;
  240   } 
  241 
  242   if (len == size) {
  243     size += 16;
  244     text = (Unicode *)grealloc(text, size * sizeof(Unicode));
  245     xRight = (double *)grealloc(xRight, size * sizeof(double));
  246   }
  247   text[len] = u;
  248   if (len == 0) {
  249     xMin = x;
  250   }
  251   xMax = xRight[len] = x + dx;
  252 //printf("added char: %f %f xright = %f\n", x, dx, x+dx);
  253   ++len;
  254 }
  255 
  256 void HtmlString::endString()
  257 {
  258   if( dir == textDirRightLeft && len > 1 )
  259   {
  260     //printf("will reverse!\n");
  261     for (int i = 0; i < len / 2; i++)
  262     {
  263       Unicode ch = text[i];
  264       text[i] = text[len - i - 1];
  265       text[len - i - 1] = ch;
  266     }
  267   }
  268 }
  269 
  270 //------------------------------------------------------------------------
  271 // HtmlPage
  272 //------------------------------------------------------------------------
  273 
  274 HtmlPage::HtmlPage(bool rawOrderA) {
  275   rawOrder = rawOrderA;
  276   curStr = nullptr;
  277   yxStrings = nullptr;
  278   xyStrings = nullptr;
  279   yxCur1 = yxCur2 = nullptr;
  280   fonts=new HtmlFontAccu();
  281   links=new HtmlLinks();
  282   imgList=new std::vector<HtmlImage*>();
  283   pageWidth=0;
  284   pageHeight=0;
  285   fontsPageMarker = 0;
  286   DocName=nullptr;
  287   firstPage = -1;
  288 }
  289 
  290 HtmlPage::~HtmlPage() {
  291   clear();
  292   delete DocName;
  293   delete fonts;
  294   delete links;
  295   for (auto entry : *imgList) {
  296     delete entry;
  297   }
  298   delete imgList;
  299 }
  300 
  301 void HtmlPage::updateFont(GfxState *state) {
  302   GfxFont *font;
  303   const char *name;
  304   int code;
  305   double w;
  306   
  307   // adjust the font size
  308   fontSize = state->getTransformedFontSize();
  309   if ((font = state->getFont()) && font->getType() == fontType3) {
  310     // This is a hack which makes it possible to deal with some Type 3
  311     // fonts.  The problem is that it's impossible to know what the
  312     // base coordinate system used in the font is without actually
  313     // rendering the font.  This code tries to guess by looking at the
  314     // width of the character 'm' (which breaks if the font is a
  315     // subset that doesn't contain 'm').
  316     for (code = 0; code < 256; ++code) {
  317       if ((name = ((Gfx8BitFont *)font)->getCharName(code)) &&
  318       name[0] == 'm' && name[1] == '\0') {
  319     break;
  320       }
  321     }
  322     if (code < 256) {
  323       w = ((Gfx8BitFont *)font)->getWidth(code);
  324       if (w != 0) {
  325     // 600 is a generic average 'm' width -- yes, this is a hack
  326     fontSize *= w / 0.6;
  327       }
  328     }
  329     const double *fm = font->getFontMatrix();
  330     if (fm[0] != 0) {
  331       fontSize *= fabs(fm[3] / fm[0]);
  332     }
  333   }
  334 }
  335 
  336 void HtmlPage::beginString(GfxState *state, const GooString *s) {
  337   curStr = new HtmlString(state, fontSize, fonts);
  338 }
  339 
  340 
  341 void HtmlPage::conv(){
  342   for(HtmlString *tmp=yxStrings;tmp;tmp=tmp->yxNext){
  343      delete tmp->htext;
  344      tmp->htext=HtmlFont::HtmlFilter(tmp->text,tmp->len);
  345 
  346      int linkIndex = 0;
  347      if (links->inLink(tmp->xMin,tmp->yMin,tmp->xMax,tmp->yMax, linkIndex)){
  348        tmp->link = links->getLink(linkIndex);
  349      }
  350   }
  351 }
  352 
  353 
  354 void HtmlPage::addChar(GfxState *state, double x, double y,
  355                double dx, double dy, 
  356             double ox, double oy, const Unicode *u, int uLen) {
  357   double x1, y1, w1, h1, dx2, dy2;
  358   int n, i;
  359   state->transform(x, y, &x1, &y1);
  360   n = curStr->len;
  361  
  362   // check that new character is in the same direction as current string
  363   // and is not too far away from it before adding 
  364   //if ((UnicodeMap::getDirection(u[0]) != curStr->dir) || 
  365   // XXX
  366   if (debug) {
  367     const double *text_mat = state->getTextMat();
  368     // rotation is (cos q, sin q, -sin q, cos q, 0, 0)
  369     // sin q is zero iff there is no rotation, or 180 deg. rotation;
  370     // for 180 rotation, cos q will be negative
  371     if (text_mat[0] < 0 || !is_within(text_mat[1], .1, 0)) {
  372       std::cerr << DEBUG << "rotation matrix for \"" << print_uni_str(u, uLen) << '"' << std::endl;
  373       std::cerr << "text " << print_matrix(state->getTextMat());
  374     }
  375   }
  376   if (n > 0 && // don't start a new string, unless there is already a string
  377       // TODO: the following line assumes that text is flowing left to
  378       // right, which will not necessarily be the case, e.g. if rotated;
  379       // It assesses whether or not two characters are close enough to
  380       // be part of the same string
  381       fabs(x1 - curStr->xRight[n-1]) > wordBreakThreshold * (curStr->yMax - curStr->yMin) &&
  382       // rotation is (cos q, sin q, -sin q, cos q, 0, 0)
  383       // sin q is zero iff there is no rotation, or 180 deg. rotation;
  384       // for 180 rotation, cos q will be negative
  385       !rot_matrices_equal(curStr->getFont().getRotMat(), state->getTextMat()))
  386   {
  387     endString();
  388     beginString(state, nullptr);
  389   }
  390   state->textTransformDelta(state->getCharSpace() * state->getHorizScaling(),
  391                 0, &dx2, &dy2);
  392   dx -= dx2;
  393   dy -= dy2;
  394   state->transformDelta(dx, dy, &w1, &h1);
  395   if (uLen != 0) {
  396     w1 /= uLen;
  397     h1 /= uLen;
  398   }
  399   for (i = 0; i < uLen; ++i) {
  400     curStr->addChar(state, x1 + i*w1, y1 + i*h1, w1, h1, u[i]);
  401   }
  402 }
  403 
  404 void HtmlPage::endString() {
  405   HtmlString *p1, *p2;
  406   double h, y1, y2;
  407 
  408   // throw away zero-length strings -- they don't have valid xMin/xMax
  409   // values, and they're useless anyway
  410   if (curStr->len == 0) {
  411     delete curStr;
  412     curStr = nullptr;
  413     return;
  414   }
  415 
  416   curStr->endString();
  417 
  418 #if 0 //~tmp
  419   if (curStr->yMax - curStr->yMin > 20) {
  420     delete curStr;
  421     curStr = NULL;
  422     return;
  423   }
  424 #endif
  425 
  426   // insert string in y-major list
  427   h = curStr->yMax - curStr->yMin;
  428   y1 = curStr->yMin + 0.5 * h;
  429   y2 = curStr->yMin + 0.8 * h;
  430   if (rawOrder) {
  431     p1 = yxCur1;
  432     p2 = nullptr;
  433   } else if ((!yxCur1 ||
  434               (y1 >= yxCur1->yMin &&
  435                (y2 >= yxCur1->yMax || curStr->xMax >= yxCur1->xMin))) &&
  436              (!yxCur2 ||
  437               (y1 < yxCur2->yMin ||
  438                (y2 < yxCur2->yMax && curStr->xMax < yxCur2->xMin)))) {
  439     p1 = yxCur1;
  440     p2 = yxCur2;
  441   } else {
  442     for (p1 = nullptr, p2 = yxStrings; p2; p1 = p2, p2 = p2->yxNext) {
  443       if (y1 < p2->yMin || (y2 < p2->yMax && curStr->xMax < p2->xMin))
  444         break;
  445     }
  446     yxCur2 = p2;
  447   }
  448   yxCur1 = curStr;
  449   if (p1)
  450     p1->yxNext = curStr;
  451   else
  452     yxStrings = curStr;
  453   curStr->yxNext = p2;
  454   curStr = nullptr;
  455 }
  456 
  457 static const char *strrstr( const char *s, const char *ss )
  458 {
  459   const char *p = strstr( s, ss );
  460   for( const char *pp = p; pp != nullptr; pp = strstr( p+1, ss ) ){
  461     p = pp;
  462   }
  463   return p;
  464 }
  465 
  466 static void CloseTags( GooString *htext, bool &finish_a, bool &finish_italic, bool &finish_bold )
  467 {
  468   const char *last_italic = finish_italic && ( finish_bold   || finish_a    ) ? strrstr( htext->c_str(), "<i>" ) : nullptr;
  469   const char *last_bold   = finish_bold   && ( finish_italic || finish_a    ) ? strrstr( htext->c_str(), "<b>" ) : nullptr;
  470   const char *last_a      = finish_a      && ( finish_italic || finish_bold ) ? strrstr( htext->c_str(), "<a " ) : nullptr;
  471   if( finish_a && ( finish_italic || finish_bold ) && last_a > ( last_italic > last_bold ? last_italic : last_bold ) ){
  472     htext->append("</a>", 4);
  473     finish_a = false;
  474   }
  475   if( finish_italic && finish_bold && last_italic > last_bold ){
  476     htext->append("</i>", 4);
  477     finish_italic = false;
  478   }
  479   if( finish_bold )
  480     htext->append("</b>", 4);
  481   if( finish_italic )
  482     htext->append("</i>", 4);
  483   if( finish_a )
  484     htext->append("</a>");
  485 }
  486 
  487 // Strings are lines of text;
  488 // This function aims to combine strings into lines and paragraphs if !noMerge
  489 // It may also strip out duplicate strings (if they are on top of each other); sometimes they are to create a font effect
  490 void HtmlPage::coalesce() {
  491   HtmlString *str1, *str2;
  492   HtmlFont *hfont1, *hfont2;
  493   double space, horSpace, vertSpace, vertOverlap;
  494   bool addSpace, addLineBreak;
  495   int n, i;
  496   double curX, curY;
  497 
  498 #if 0 //~ for debugging
  499   for (str1 = yxStrings; str1; str1 = str1->yxNext) {
  500     printf("x=%f..%f  y=%f..%f  size=%2d '",
  501        str1->xMin, str1->xMax, str1->yMin, str1->yMax,
  502        (int)(str1->yMax - str1->yMin));
  503     for (i = 0; i < str1->len; ++i) {
  504       fputc(str1->text[i] & 0xff, stdout);
  505     }
  506     printf("'\n");
  507   }
  508   printf("\n------------------------------------------------------------\n\n");
  509 #endif
  510   str1 = yxStrings;
  511 
  512   if( !str1 ) return;
  513 
  514   //----- discard duplicated text (fake boldface, drop shadows)
  515   if( !complexMode )
  516   { /* if not in complex mode get rid of duplicate strings */
  517     HtmlString *str3;
  518     bool found;
  519     while (str1)
  520     {
  521         double size = str1->yMax - str1->yMin;
  522         double xLimit = str1->xMin + size * 0.2;
  523         found = false;
  524         for (str2 = str1, str3 = str1->yxNext;
  525             str3 && str3->xMin < xLimit;
  526             str2 = str3, str3 = str2->yxNext)
  527         {
  528             if (str3->len == str1->len &&
  529                 !memcmp(str3->text, str1->text, str1->len * sizeof(Unicode)) &&
  530                 fabs(str3->yMin - str1->yMin) < size * 0.2 &&
  531                 fabs(str3->yMax - str1->yMax) < size * 0.2 &&
  532                 fabs(str3->xMax - str1->xMax) < size * 0.2)
  533             {
  534                 found = true;
  535                 //printf("found duplicate!\n");
  536                 break;
  537             }
  538         }
  539         if (found)
  540         {
  541             str2->xyNext = str3->xyNext;
  542             str2->yxNext = str3->yxNext;
  543             delete str3;
  544         }
  545         else
  546         {
  547             str1 = str1->yxNext;
  548         }
  549     }       
  550   } /*- !complexMode */
  551   
  552   str1 = yxStrings;
  553   
  554   hfont1 = getFont(str1);
  555   if( hfont1->isBold() )
  556     str1->htext->insert(0,"<b>",3);
  557   if( hfont1->isItalic() )
  558     str1->htext->insert(0,"<i>",3);
  559   if( str1->getLink() != nullptr ) {
  560     GooString *ls = str1->getLink()->getLinkStart();
  561     str1->htext->insert(0, ls);
  562     delete ls;
  563   }
  564   curX = str1->xMin; curY = str1->yMin;
  565 
  566   while (str1 && (str2 = str1->yxNext)) {
  567     hfont2 = getFont(str2);
  568     space = str1->yMax - str1->yMin; // the height of the font's bounding box
  569     horSpace = str2->xMin - str1->xMax;
  570     // if strings line up on left-hand side AND they are on subsequent lines, we need a line break
  571     addLineBreak = !noMerge && (fabs(str1->xMin - str2->xMin) < 0.4) && IS_CLOSER(str2->yMax, str1->yMax + space, str1->yMax);
  572     vertSpace = str2->yMin - str1->yMax;
  573 
  574 //printf("coalesce %d %d %f? ", str1->dir, str2->dir, d);
  575 
  576     if (str2->yMin >= str1->yMin && str2->yMin <= str1->yMax)
  577     {
  578     vertOverlap = str1->yMax - str2->yMin;
  579     } else
  580     if (str2->yMax >= str1->yMin && str2->yMax <= str1->yMax)
  581     {
  582     vertOverlap = str2->yMax - str1->yMin;
  583     } else
  584     {
  585         vertOverlap = 0;
  586     } 
  587     
  588     // Combine strings if:
  589     //  They appear to be the same font (complex mode only) && going in the same direction AND at least one of the following:
  590     //  1.  They appear to be part of the same line of text
  591     //  2.  They appear to be subsequent lines of a paragraph
  592     //  We assume (1) or (2) above, respectively, based on:
  593     //  (1)  strings overlap vertically AND
  594     //       horizontal space between end of str1 and start of str2 is consistent with a single space or less;
  595     //       when rawOrder, the strings have to overlap vertically by at least 50%
  596     //  (2)  Strings flow down the page, but the space between them is not too great, and they are lined up on the left
  597     if (
  598     (
  599      (
  600       (
  601        (rawOrder && vertOverlap > 0.5 * space) 
  602        ||
  603        (!rawOrder && str2->yMin < str1->yMax)
  604       ) &&
  605       (horSpace > -0.5 * space && horSpace < space)
  606      ) ||
  607          (vertSpace >= 0 && vertSpace < 0.5 * space && addLineBreak)
  608     ) &&
  609     (!complexMode || (hfont1->isEqualIgnoreBold(*hfont2))) && // in complex mode fonts must be the same, in other modes fonts do not metter
  610     str1->dir == str2->dir // text direction the same
  611        ) 
  612     {
  613 //      printf("yes\n");
  614       n = str1->len + str2->len;
  615       if ((addSpace = horSpace > wordBreakThreshold * space)) {
  616         ++n;
  617       }
  618       if (addLineBreak) {
  619         ++n;
  620       }
  621   
  622       str1->size = (n + 15) & ~15;
  623       str1->text = (Unicode *)grealloc(str1->text,
  624                        str1->size * sizeof(Unicode));
  625       str1->xRight = (double *)grealloc(str1->xRight,
  626                     str1->size * sizeof(double));
  627       if (addSpace) {
  628           str1->text[str1->len] = 0x20;
  629           str1->htext->append(xml?" ":"&#160;");
  630           str1->xRight[str1->len] = str2->xMin;
  631           ++str1->len;
  632       }
  633       if (addLineBreak) {
  634       str1->text[str1->len] = '\n';
  635       str1->htext->append("<br/>");
  636       str1->xRight[str1->len] = str2->xMin;
  637       ++str1->len;
  638       str1->yMin = str2->yMin;
  639       str1->yMax = str2->yMax;
  640       str1->xMax = str2->xMax;
  641       int fontLineSize = hfont1->getLineSize();
  642       int curLineSize = (int)(vertSpace + space); 
  643       if( curLineSize != fontLineSize )
  644       {
  645           HtmlFont *newfnt = new HtmlFont(*hfont1);
  646           newfnt->setLineSize(curLineSize);
  647           str1->fontpos = fonts->AddFont(*newfnt);
  648           delete newfnt;
  649           hfont1 = getFont(str1);
  650           // we have to reget hfont2 because it's location could have
  651           // changed on resize
  652           hfont2 = getFont(str2); 
  653       }
  654       }
  655       for (i = 0; i < str2->len; ++i) {
  656     str1->text[str1->len] = str2->text[i];
  657     str1->xRight[str1->len] = str2->xRight[i];
  658     ++str1->len;
  659       }
  660 
  661       /* fix <i>, <b> if str1 and str2 differ and handle switch of links */
  662       HtmlLink *hlink1 = str1->getLink();
  663       HtmlLink *hlink2 = str2->getLink();
  664       bool switch_links = !hlink1 || !hlink2 || !hlink1->isEqualDest(*hlink2);
  665       bool finish_a = switch_links && hlink1 != nullptr;
  666       bool finish_italic = hfont1->isItalic() && ( !hfont2->isItalic() || finish_a );
  667       bool finish_bold   = hfont1->isBold()   && ( !hfont2->isBold()   || finish_a || finish_italic );
  668       CloseTags( str1->htext, finish_a, finish_italic, finish_bold );
  669       if( switch_links && hlink2 != nullptr ) {
  670         GooString *ls = hlink2->getLinkStart();
  671         str1->htext->append(ls);
  672         delete ls;
  673       }
  674       if( ( !hfont1->isItalic() || finish_italic ) && hfont2->isItalic() )
  675         str1->htext->append("<i>", 3);
  676       if( ( !hfont1->isBold() || finish_bold ) && hfont2->isBold() )
  677         str1->htext->append("<b>", 3);
  678 
  679 
  680       str1->htext->append(str2->htext);
  681       // str1 now contains href for link of str2 (if it is defined)
  682       str1->link = str2->link; 
  683       hfont1 = hfont2;
  684       if (str2->xMax > str1->xMax) {
  685     str1->xMax = str2->xMax;
  686       }
  687       if (str2->yMax > str1->yMax) {
  688     str1->yMax = str2->yMax;
  689       }
  690       str1->yxNext = str2->yxNext;
  691       delete str2;
  692     } else { // keep strings separate
  693 //      printf("no\n"); 
  694       bool finish_a = str1->getLink() != nullptr;
  695       bool finish_bold   = hfont1->isBold();
  696       bool finish_italic = hfont1->isItalic();
  697       CloseTags( str1->htext, finish_a, finish_italic, finish_bold );
  698      
  699       str1->xMin = curX; str1->yMin = curY; 
  700       str1 = str2;
  701       curX = str1->xMin; curY = str1->yMin;
  702       hfont1 = hfont2;
  703       if( hfont1->isBold() )
  704     str1->htext->insert(0,"<b>",3);
  705       if( hfont1->isItalic() )
  706     str1->htext->insert(0,"<i>",3);
  707       if( str1->getLink() != nullptr ) {
  708     GooString *ls = str1->getLink()->getLinkStart();
  709     str1->htext->insert(0, ls);
  710     delete ls;
  711       }
  712     }
  713   }
  714   str1->xMin = curX; str1->yMin = curY;
  715 
  716   bool finish_bold   = hfont1->isBold();
  717   bool finish_italic = hfont1->isItalic();
  718   bool finish_a = str1->getLink() != nullptr;
  719   CloseTags( str1->htext, finish_a, finish_italic, finish_bold );
  720 
  721 #if 0 //~ for debugging
  722   for (str1 = yxStrings; str1; str1 = str1->yxNext) {
  723     printf("x=%3d..%3d  y=%3d..%3d  size=%2d ",
  724        (int)str1->xMin, (int)str1->xMax, (int)str1->yMin, (int)str1->yMax,
  725        (int)(str1->yMax - str1->yMin));
  726     printf("'%s'\n", str1->htext->c_str());  
  727   }
  728   printf("\n------------------------------------------------------------\n\n");
  729 #endif
  730 
  731 }
  732 
  733 void HtmlPage::dumpAsXML(FILE* f,int page){  
  734   fprintf(f, "<page number=\"%d\" position=\"absolute\"", page);
  735   fprintf(f," top=\"0\" left=\"0\" height=\"%d\" width=\"%d\">\n", pageHeight,pageWidth);
  736     
  737   for(int i=fontsPageMarker;i < fonts->size();i++) {
  738     GooString *fontCSStyle = fonts->CSStyle(i);
  739     fprintf(f,"\t%s\n",fontCSStyle->c_str());
  740     delete fontCSStyle;
  741   }
  742 
  743   for (auto ptr : *imgList) {
  744     auto img = static_cast<HtmlImage *>(ptr);
  745     if (!noRoundedCoordinates) {
  746       fprintf(f, "<image top=\"%d\" left=\"%d\" ", xoutRound(img->yMin), xoutRound(img->xMin));
  747       fprintf(f, "width=\"%d\" height=\"%d\" ", xoutRound(img->xMax - img->xMin), xoutRound(img->yMax - img->yMin));
  748     }
  749     else {
  750       fprintf(f, "<image top=\"%f\" left=\"%f\" ", img->yMin, img->xMin);
  751       fprintf(f, "width=\"%f\" height=\"%f\" ", img->xMax - img->xMin, img->yMax - img->yMin);
  752     }
  753     fprintf(f,"src=\"%s\"/>\n",img->fName->c_str());
  754     delete img;
  755   }
  756   imgList->clear();
  757 
  758   for(HtmlString *tmp=yxStrings;tmp;tmp=tmp->yxNext){
  759     if (tmp->htext){
  760       if (!noRoundedCoordinates) {
  761         fprintf(f, "<text top=\"%d\" left=\"%d\" ", xoutRound(tmp->yMin), xoutRound(tmp->xMin));
  762         fprintf(f, "width=\"%d\" height=\"%d\" ", xoutRound(tmp->xMax - tmp->xMin), xoutRound(tmp->yMax - tmp->yMin));
  763       }
  764       else {
  765         fprintf(f, "<text top=\"%f\" left=\"%f\" ", tmp->yMin, tmp->xMin);
  766         fprintf(f, "width=\"%f\" height=\"%f\" ", tmp->xMax - tmp->xMin, tmp->yMax - tmp->yMin);
  767       }
  768       fprintf(f,"font=\"%d\">", tmp->fontpos);
  769       fputs(tmp->htext->c_str(),f);
  770       fputs("</text>\n",f);
  771     }
  772   }
  773   fputs("</page>\n",f);
  774 }
  775 
  776 static void printCSS(FILE *f)
  777 {
  778   // Image flip/flop CSS
  779   // Source:
  780   // http://stackoverflow.com/questions/1309055/cross-browser-way-to-flip-html-image-via-javascript-css
  781   // tested in Chrome, Fx (Linux) and IE9 (W7)
  782   static const char css[] = 
  783     "<style type=\"text/css\">" "\n"
  784     "<!--" "\n"
  785     ".xflip {" "\n"
  786     "    -moz-transform: scaleX(-1);" "\n"
  787     "    -webkit-transform: scaleX(-1);" "\n"
  788     "    -o-transform: scaleX(-1);" "\n"
  789     "    transform: scaleX(-1);" "\n"
  790     "    filter: fliph;" "\n"
  791     "}" "\n"
  792     ".yflip {" "\n"
  793     "    -moz-transform: scaleY(-1);" "\n"
  794     "    -webkit-transform: scaleY(-1);" "\n"
  795     "    -o-transform: scaleY(-1);" "\n"
  796     "    transform: scaleY(-1);" "\n"
  797     "    filter: flipv;" "\n"
  798     "}" "\n"
  799     ".xyflip {" "\n"
  800     "    -moz-transform: scaleX(-1) scaleY(-1);" "\n"
  801     "    -webkit-transform: scaleX(-1) scaleY(-1);" "\n"
  802     "    -o-transform: scaleX(-1) scaleY(-1);" "\n"
  803     "    transform: scaleX(-1) scaleY(-1);" "\n"
  804     "    filter: fliph + flipv;" "\n"
  805     "}" "\n"
  806     "-->" "\n"
  807     "</style>" "\n";
  808 
  809   fwrite( css, sizeof(css)-1, 1, f );
  810 }
  811 
  812 int HtmlPage::dumpComplexHeaders(FILE * const file, FILE *& pageFile, int page) {
  813   GooString* tmp;
  814 
  815   if( !noframes )
  816   {
  817       GooString* pgNum=GooString::fromInt(page);
  818       tmp = new GooString(DocName);
  819       if (!singleHtml){
  820             tmp->append('-')->append(pgNum)->append(".html");
  821             pageFile = fopen(tmp->c_str(), "w");
  822       } else {
  823             tmp->append("-html")->append(".html");
  824             pageFile = fopen(tmp->c_str(), "a");
  825       }
  826       delete pgNum;
  827       if (!pageFile) {
  828       error(errIO, -1, "Couldn't open html file '{0:t}'", tmp);
  829       delete tmp;
  830       return 1;
  831       } 
  832 
  833       if (!singleHtml)
  834         fprintf(pageFile,"%s\n<html xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"\" xml:lang=\"\">\n<head>\n<title>Page %d</title>\n\n", DOCTYPE, page);
  835       else
  836         fprintf(pageFile,"%s\n<html xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"\" xml:lang=\"\">\n<head>\n<title>%s</title>\n\n", DOCTYPE, tmp->c_str());
  837 
  838       delete tmp;
  839 
  840       GooString *htmlEncoding = HtmlOutputDev::mapEncodingToHtml(globalParams->getTextEncodingName());
  841       if (!singleHtml)
  842         fprintf(pageFile, "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=%s\"/>\n", htmlEncoding->c_str());
  843       else
  844         fprintf(pageFile, "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=%s\"/>\n <br/>\n", htmlEncoding->c_str());
  845       delete htmlEncoding;
  846   }
  847   else 
  848   {
  849       pageFile = file;
  850       fprintf(pageFile,"<!-- Page %d -->\n", page);
  851       fprintf(pageFile,"<a name=\"%d\"></a>\n", page);
  852   } 
  853   
  854   return 0;
  855 }
  856 
  857 void HtmlPage::dumpComplex(FILE *file, int page, const std::vector<std::string>& backgroundImages) {
  858   FILE* pageFile;
  859 
  860   if( firstPage == -1 ) firstPage = page; 
  861   
  862   if (dumpComplexHeaders(file, pageFile, page)) { error(errIO, -1, "Couldn't write headers."); return; }
  863    
  864   fputs("<style type=\"text/css\">\n<!--\n",pageFile);
  865   fputs("\tp {margin: 0; padding: 0;}",pageFile);
  866   for(int i=fontsPageMarker;i!=fonts->size();i++) {
  867     GooString *fontCSStyle;
  868     if (!singleHtml)
  869          fontCSStyle = fonts->CSStyle(i);
  870     else
  871          fontCSStyle = fonts->CSStyle(i,page);
  872     fprintf(pageFile,"\t%s\n",fontCSStyle->c_str());
  873     delete fontCSStyle;
  874   }
  875  
  876   fputs("-->\n</style>\n",pageFile);
  877   
  878   if( !noframes )
  879   {  
  880       fputs("</head>\n<body bgcolor=\"#A0A0A0\" vlink=\"blue\" link=\"blue\">\n",pageFile); 
  881   }
  882   
  883   fprintf(pageFile,"<div id=\"page%d-div\" style=\"position:relative;width:%dpx;height:%dpx;\">\n",
  884       page, pageWidth, pageHeight);
  885 
  886   if(!ignore && (size_t) (page - firstPage) < backgroundImages.size())
  887   {
  888     fprintf(pageFile,
  889       "<img width=\"%d\" height=\"%d\" src=\"%s\" alt=\"background image\"/>\n",
  890       pageWidth, pageHeight, backgroundImages[page - firstPage].c_str());
  891   }
  892   
  893   for(HtmlString *tmp1=yxStrings;tmp1;tmp1=tmp1->yxNext){
  894     if (tmp1->htext){
  895       fprintf(pageFile,
  896           "<p style=\"position:absolute;top:%dpx;left:%dpx;white-space:nowrap\" class=\"ft",
  897           xoutRound(tmp1->yMin),
  898           xoutRound(tmp1->xMin));
  899       if (!singleHtml) {
  900           fputc('0', pageFile);
  901       } else {
  902           fprintf(pageFile, "%d", page);
  903       }
  904       fprintf(pageFile,"%d\">", tmp1->fontpos);
  905       fputs(tmp1->htext->c_str(), pageFile);
  906       fputs("</p>\n", pageFile);
  907     }
  908   }
  909 
  910   fputs("</div>\n", pageFile);
  911   
  912   if( !noframes )
  913   {
  914       fputs("</body>\n</html>\n",pageFile);
  915       fclose(pageFile);
  916   }
  917 }
  918 
  919 
  920 void HtmlPage::dump(FILE *f, int pageNum, const std::vector<std::string>& backgroundImages)
  921 {
  922   if (complexMode || singleHtml)
  923   {
  924     if (xml) dumpAsXML(f, pageNum);
  925     if (!xml) dumpComplex(f, pageNum, backgroundImages);
  926   }
  927   else
  928   {
  929     fprintf(f,"<a name=%d></a>",pageNum);
  930     // Loop over the list of image names on this page
  931     for (auto ptr : *imgList) {
  932       auto img = static_cast<HtmlImage *>(ptr);
  933 
  934       // see printCSS() for class names
  935       const char *styles[4] = { "", " class=\"xflip\"", " class=\"yflip\"", " class=\"xyflip\"" };
  936       int style_index=0;
  937       if (img->xMin > img->xMax) style_index += 1; // xFlip
  938       if (img->yMin > img->yMax) style_index += 2; // yFlip
  939 
  940       fprintf(f,"<img%s src=\"%s\"/><br/>\n",styles[style_index],img->fName->c_str());
  941       delete img;
  942     }
  943     imgList->clear();
  944 
  945     GooString* str;
  946     for(HtmlString *tmp=yxStrings;tmp;tmp=tmp->yxNext){
  947       if (tmp->htext){
  948         str=new GooString(tmp->htext); 
  949         fputs(str->c_str(),f);
  950         delete str;      
  951         fputs("<br/>\n",f);
  952       }
  953     }
  954     fputs("<hr/>\n",f);  
  955   }
  956 }
  957 
  958 
  959 
  960 void HtmlPage::clear() {
  961   HtmlString *p1, *p2;
  962 
  963   if (curStr) {
  964     delete curStr;
  965     curStr = nullptr;
  966   }
  967   for (p1 = yxStrings; p1; p1 = p2) {
  968     p2 = p1->yxNext;
  969     delete p1;
  970   }
  971   yxStrings = nullptr;
  972   xyStrings = nullptr;
  973   yxCur1 = yxCur2 = nullptr;
  974 
  975   if( !noframes )
  976   {
  977       delete fonts;
  978       fonts=new HtmlFontAccu();
  979       fontsPageMarker = 0;
  980   }
  981   else
  982   {
  983       fontsPageMarker = fonts->size();
  984   }
  985 
  986   delete links;
  987   links=new HtmlLinks();
  988  
  989 
  990 }
  991 
  992 void HtmlPage::setDocName(const char *fname){
  993   DocName=new GooString(fname);
  994 }
  995 
  996 void HtmlPage::addImage(GooString *fname, GfxState *state) {
  997   HtmlImage *img = new HtmlImage(fname, state);
  998   imgList->push_back(img);
  999 }
 1000 
 1001 //------------------------------------------------------------------------
 1002 // HtmlMetaVar
 1003 //------------------------------------------------------------------------
 1004 
 1005 HtmlMetaVar::HtmlMetaVar(const char *_name, const char *_content)
 1006 {
 1007     name = new GooString(_name);
 1008     content = new GooString(_content);
 1009 }
 1010 
 1011 HtmlMetaVar::~HtmlMetaVar()
 1012 {
 1013    delete name;
 1014    delete content;
 1015 } 
 1016     
 1017 GooString* HtmlMetaVar::toString()  
 1018 {
 1019     GooString *result = new GooString("<meta name=\"");
 1020     result->append(name);
 1021     result->append("\" content=\"");
 1022     result->append(content);
 1023     result->append("\"/>");
 1024     return result;
 1025 }
 1026 
 1027 //------------------------------------------------------------------------
 1028 // HtmlOutputDev
 1029 //------------------------------------------------------------------------
 1030 
 1031 static const char* HtmlEncodings[][2] = {
 1032     {"Latin1", "ISO-8859-1"},
 1033     {nullptr, nullptr}
 1034 };
 1035 
 1036 GooString* HtmlOutputDev::mapEncodingToHtml(GooString* encoding)
 1037 {
 1038   GooString* enc = encoding;
 1039   for(int i = 0; HtmlEncodings[i][0] != nullptr; i++)
 1040   {
 1041     if( enc->cmp(HtmlEncodings[i][0]) == 0 )
 1042     {
 1043       delete enc;
 1044       return new GooString(HtmlEncodings[i][1]);
 1045     }
 1046   }
 1047   return enc; 
 1048 }
 1049 
 1050 void HtmlOutputDev::doFrame(int firstPage){
 1051   GooString* fName=new GooString(Docname);
 1052   GooString* htmlEncoding;
 1053   fName->append(".html");
 1054 
 1055   if (!(fContentsFrame = fopen(fName->c_str(), "w"))){
 1056     error(errIO, -1, "Couldn't open html file '{0:t}'", fName);
 1057     delete fName;
 1058     return;
 1059   }
 1060   
 1061   delete fName;
 1062     
 1063   const std::string baseName = gbasename(Docname->c_str());
 1064   fputs(DOCTYPE, fContentsFrame);
 1065   fputs("\n<html>",fContentsFrame);
 1066   fputs("\n<head>",fContentsFrame);
 1067   fprintf(fContentsFrame,"\n<title>%s</title>",docTitle->c_str());
 1068   htmlEncoding = mapEncodingToHtml(globalParams->getTextEncodingName());
 1069   fprintf(fContentsFrame, "\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=%s\"/>\n", htmlEncoding->c_str());
 1070   dumpMetaVars(fContentsFrame);
 1071   fprintf(fContentsFrame, "</head>\n");
 1072   fputs("<frameset cols=\"100,*\">\n",fContentsFrame);
 1073   fprintf(fContentsFrame,"<frame name=\"links\" src=\"%s_ind.html\"/>\n", baseName.c_str());
 1074   fputs("<frame name=\"contents\" src=",fContentsFrame); 
 1075   if (complexMode) 
 1076       fprintf(fContentsFrame,"\"%s-%d.html\"", baseName.c_str(), firstPage);
 1077   else
 1078       fprintf(fContentsFrame,"\"%ss.html\"", baseName.c_str());
 1079   
 1080   fputs("/>\n</frameset>\n</html>\n",fContentsFrame);
 1081  
 1082   delete htmlEncoding;
 1083   fclose(fContentsFrame);  
 1084 }
 1085 
 1086 HtmlOutputDev::HtmlOutputDev(Catalog *catalogA, const char *fileName, const char *title,
 1087     const char *author, const char *keywords, const char *subject, const char *date,
 1088     bool rawOrderA, int firstPage, bool outline)
 1089 {
 1090   catalog = catalogA;
 1091   fContentsFrame = nullptr;
 1092   page = nullptr;
 1093   docTitle = new GooString(title);
 1094   pages = nullptr;
 1095   dumpJPEG=true;
 1096   //write = true;
 1097   rawOrder = rawOrderA;
 1098   this->doOutline = outline;
 1099   ok = false;
 1100   //this->firstPage = firstPage;
 1101   //pageNum=firstPage;
 1102   // open file
 1103   needClose = false;
 1104   pages = new HtmlPage(rawOrder);
 1105   
 1106   glMetaVars = new std::vector<HtmlMetaVar*>();
 1107   glMetaVars->push_back(new HtmlMetaVar("generator", "pdftohtml 0.36"));
 1108   if( author ) glMetaVars->push_back(new HtmlMetaVar("author", author));
 1109   if( keywords ) glMetaVars->push_back(new HtmlMetaVar("keywords", keywords));
 1110   if( date ) glMetaVars->push_back(new HtmlMetaVar("date", date));
 1111   if( subject ) glMetaVars->push_back(new HtmlMetaVar("subject", subject));
 1112 
 1113   maxPageWidth = 0;
 1114   maxPageHeight = 0;
 1115 
 1116   pages->setDocName(fileName);
 1117   Docname=new GooString (fileName);
 1118 
 1119   // for non-xml output (complex or simple) with frames generate the left frame
 1120   if(!xml && !noframes)
 1121   {
 1122      if (!singleHtml)
 1123      {
 1124          GooString* left=new GooString(fileName);
 1125          left->append("_ind.html");
 1126 
 1127          doFrame(firstPage);
 1128 
 1129          if (!(fContentsFrame = fopen(left->c_str(), "w")))
 1130          {
 1131              error(errIO, -1, "Couldn't open html file '{0:t}'", left);
 1132              delete left;
 1133              return;
 1134          }
 1135          delete left;
 1136          fputs(DOCTYPE, fContentsFrame);
 1137          fputs("<html xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"\" xml:lang=\"\">\n<head>\n<title></title>\n</head>\n<body>\n", fContentsFrame);
 1138 
 1139          if (doOutline)
 1140          {
 1141              fprintf(fContentsFrame, "<a href=\"%s%s\" target=\"contents\">Outline</a><br/>",
 1142                  gbasename(Docname->c_str()).c_str(),
 1143                  complexMode ? "-outline.html" : "s.html#outline");
 1144          }
 1145      }
 1146     if (!complexMode)
 1147     {   /* not in complex mode */
 1148         
 1149        GooString* right=new GooString(fileName);
 1150        right->append("s.html");
 1151 
 1152        if (!(page=fopen(right->c_str(),"w"))){
 1153         error(errIO, -1, "Couldn't open html file '{0:t}'", right);
 1154         delete right;
 1155         return;
 1156        }
 1157        delete right;
 1158        fputs(DOCTYPE, page);
 1159        fputs("<html>\n<head>\n<title></title>\n",page);
 1160        printCSS(page);
 1161        fputs("</head>\n<body>\n",page);
 1162      }
 1163   }
 1164 
 1165   if (noframes) {
 1166     if (stout) page=stdout;
 1167     else {
 1168       GooString* right=new GooString(fileName);
 1169       if (!xml) right->append(".html");
 1170       if (xml) right->append(".xml");
 1171       if (!(page=fopen(right->c_str(),"w"))){
 1172     error(errIO, -1, "Couldn't open html file '{0:t}'", right);
 1173     delete right;
 1174     return;
 1175       }  
 1176       delete right;
 1177     }
 1178 
 1179     GooString *htmlEncoding = mapEncodingToHtml(globalParams->getTextEncodingName()); 
 1180     if (xml) 
 1181     {
 1182       fprintf(page, "<?xml version=\"1.0\" encoding=\"%s\"?>\n", htmlEncoding->c_str());
 1183       fputs("<!DOCTYPE pdf2xml SYSTEM \"pdf2xml.dtd\">\n\n", page);
 1184       fprintf(page,"<pdf2xml producer=\"%s\" version=\"%s\">\n", PACKAGE_NAME, PACKAGE_VERSION);
 1185     } 
 1186     else 
 1187     {
 1188       fprintf(page,"%s\n<html xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"\" xml:lang=\"\">\n<head>\n<title>%s</title>\n", DOCTYPE, docTitle->c_str());
 1189       
 1190       fprintf(page, "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=%s\"/>\n", htmlEncoding->c_str());
 1191       
 1192       dumpMetaVars(page);
 1193       printCSS(page);
 1194       fprintf(page,"</head>\n");
 1195       fprintf(page,"<body bgcolor=\"#A0A0A0\" vlink=\"blue\" link=\"blue\">\n");
 1196     }
 1197     delete htmlEncoding;
 1198   }
 1199   ok = true; 
 1200 }
 1201 
 1202 HtmlOutputDev::~HtmlOutputDev() {
 1203     delete Docname;
 1204     delete docTitle;
 1205 
 1206     for (auto entry : *glMetaVars) {
 1207       delete entry;
 1208     }
 1209     delete glMetaVars;
 1210 
 1211     if (fContentsFrame){
 1212       fputs("</body>\n</html>\n",fContentsFrame);  
 1213       fclose(fContentsFrame);
 1214     }
 1215     if (page != nullptr) {
 1216       if (xml) {
 1217         fputs("</pdf2xml>\n",page);  
 1218         fclose(page);
 1219       } else
 1220       if ( !complexMode || xml || noframes )
 1221       { 
 1222         fputs("</body>\n</html>\n",page);  
 1223         fclose(page);
 1224       }
 1225     }
 1226     if (pages)
 1227       delete pages;
 1228 }
 1229 
 1230 void HtmlOutputDev::startPage(int pageNumA, GfxState *state, XRef *xref) {
 1231 #if 0
 1232   if (mode&&!xml){
 1233     if (write){
 1234       write=false;
 1235       GooString* fname=Dirname(Docname);
 1236       fname->append("image.log");
 1237       if((tin=fopen(getFileNameFromPath(fname->c_str(),fname->getLength()),"w"))==NULL){
 1238     printf("Error : can not open %s",fname);
 1239     exit(1);
 1240       }
 1241       delete fname;
 1242     // if(state->getRotation()!=0) 
 1243     //  fprintf(tin,"ROTATE=%d rotate %d neg %d neg translate\n",state->getRotation(),state->getX1(),-state->getY1());
 1244     // else 
 1245       fprintf(tin,"ROTATE=%d neg %d neg translate\n",state->getX1(),state->getY1());  
 1246     }
 1247   }
 1248 #endif
 1249 
 1250   pageNum = pageNumA;
 1251   const std::string str = gbasename(Docname->c_str());
 1252   pages->clear(); 
 1253   if(!noframes)
 1254   {
 1255     if (fContentsFrame)
 1256     {
 1257       if (complexMode)
 1258         fprintf(fContentsFrame,"<a href=\"%s-%d.html\"", str.c_str(), pageNum);
 1259       else 
 1260         fprintf(fContentsFrame,"<a href=\"%ss.html#%d\"", str.c_str(), pageNum);
 1261       fprintf(fContentsFrame," target=\"contents\" >Page %d</a><br/>\n",pageNum);
 1262     }
 1263   }
 1264 
 1265   pages->pageWidth=static_cast<int>(state->getPageWidth());
 1266   pages->pageHeight=static_cast<int>(state->getPageHeight());
 1267 } 
 1268 
 1269 
 1270 void HtmlOutputDev::endPage() {
 1271   Links *linksList = docPage->getLinks();
 1272   for (int i = 0; i < linksList->getNumLinks(); ++i)
 1273   {
 1274       doProcessLink(linksList->getLink(i));
 1275   }
 1276   delete linksList;
 1277 
 1278   pages->conv();
 1279   pages->coalesce();
 1280   pages->dump(page, pageNum, backgroundImages);
 1281   
 1282   // I don't yet know what to do in the case when there are pages of different
 1283   // sizes and we want complex output: running ghostscript many times 
 1284   // seems very inefficient. So for now I'll just use last page's size
 1285   maxPageWidth = pages->pageWidth;
 1286   maxPageHeight = pages->pageHeight;
 1287   
 1288   //if(!noframes&&!xml) fputs("<br/>\n", fContentsFrame);
 1289   if(!stout && !globalParams->getErrQuiet()) printf("Page-%d\n",(pageNum));
 1290 }
 1291 
 1292 void HtmlOutputDev::addBackgroundImage(const std::string& img) {
 1293   backgroundImages.push_back(img);
 1294 }
 1295 
 1296 void HtmlOutputDev::updateFont(GfxState *state) {
 1297   pages->updateFont(state);
 1298 }
 1299 
 1300 void HtmlOutputDev::beginString(GfxState *state, const GooString *s) {
 1301   pages->beginString(state, s);
 1302 }
 1303 
 1304 void HtmlOutputDev::endString(GfxState *state) {
 1305   pages->endString();
 1306 }
 1307 
 1308 void HtmlOutputDev::drawChar(GfxState *state, double x, double y,
 1309           double dx, double dy,
 1310           double originX, double originY,
 1311           CharCode code, int /*nBytes*/, const Unicode *u, int uLen)
 1312 {
 1313   if ( !showHidden && (state->getRender() & 3) == 3) {
 1314     return;
 1315   }
 1316   pages->addChar(state, x, y, dx, dy, originX, originY, u, uLen);
 1317 }
 1318 
 1319 void HtmlOutputDev::drawJpegImage(GfxState *state, Stream *str)
 1320 {
 1321   InMemoryFile ims;
 1322   FILE *f1 = nullptr;
 1323   int c;
 1324 
 1325   // open the image file
 1326   GooString *fName = createImageFileName("jpg");
 1327   f1 = dataUrls ? ims.open("wb") : fopen(fName->c_str(), "wb");
 1328   if (!f1) {
 1329     error(errIO, -1, "Couldn't open image file '{0:t}'", fName);
 1330     delete fName;
 1331     return;
 1332   }
 1333 
 1334   // initialize stream
 1335   str = str->getNextStream();
 1336   str->reset();
 1337 
 1338   // copy the stream
 1339   while ((c = str->getChar()) != EOF)
 1340     fputc(c, f1);
 1341 
 1342   fclose(f1);
 1343 
 1344   if (dataUrls) {
 1345     delete fName;
 1346     fName = new GooString(std::string("data:image/jpeg;base64,") + gbase64Encode(ims.getBuffer()));
 1347   }
 1348   pages->addImage(fName, state);
 1349 }
 1350 
 1351 void HtmlOutputDev::drawPngImage(GfxState *state, Stream *str, int width, int height,
 1352                                  GfxImageColorMap *colorMap, bool isMask)
 1353 {
 1354 #ifdef ENABLE_LIBPNG
 1355   FILE *f1;
 1356   InMemoryFile ims;
 1357 
 1358   if (!colorMap && !isMask) {
 1359     error(errInternal, -1, "Can't have color image without a color map");
 1360     return;
 1361   }
 1362 
 1363   // open the image file
 1364   GooString *fName=createImageFileName("png");
 1365   f1 = dataUrls ? ims.open("wb") : fopen(fName->c_str(), "wb");
 1366   if (!f1) {
 1367     error(errIO, -1, "Couldn't open image file '{0:t}'", fName);
 1368     delete fName;
 1369     return;
 1370   }
 1371 
 1372   PNGWriter *writer = new PNGWriter( isMask ? PNGWriter::MONOCHROME : PNGWriter::RGB );
 1373   // TODO can we calculate the resolution of the image?
 1374   if (!writer->init(f1, width, height, 72, 72)) {
 1375     error(errInternal, -1, "Can't init PNG for image '{0:t}'", fName);
 1376     delete writer;
 1377     fclose(f1);
 1378     return;
 1379   }
 1380 
 1381   if (!isMask) {
 1382     unsigned char *p;
 1383     GfxRGB rgb;
 1384     png_byte *row = (png_byte *) gmalloc(3 * width);   // 3 bytes/pixel: RGB
 1385     png_bytep *row_pointer= &row;
 1386 
 1387     // Initialize the image stream
 1388     ImageStream *imgStr = new ImageStream(str, width,
 1389                         colorMap->getNumPixelComps(), colorMap->getBits());
 1390     imgStr->reset();
 1391 
 1392     // For each line...
 1393     for (int y = 0; y < height; y++) {
 1394 
 1395       // Convert into a PNG row
 1396       p = imgStr->getLine();
 1397       if (!p) {
 1398         error(errIO, -1, "Failed to read PNG. '{0:t}' will be incorrect", fName);
 1399         delete fName;
 1400         gfree(row);
 1401         delete writer;
 1402         delete imgStr;
 1403         fclose(f1);
 1404         return;
 1405       }
 1406       for (int x = 0; x < width; x++) {
 1407         colorMap->getRGB(p, &rgb);
 1408         // Write the RGB pixels into the row
 1409         row[3*x]= colToByte(rgb.r);
 1410         row[3*x+1]= colToByte(rgb.g);
 1411         row[3*x+2]= colToByte(rgb.b);
 1412         p += colorMap->getNumPixelComps();
 1413       }
 1414 
 1415       if (!writer->writeRow(row_pointer)) {
 1416         error(errIO, -1, "Failed to write into PNG '{0:t}'", fName);
 1417         delete writer;
 1418         delete imgStr;
 1419         fclose(f1);
 1420         return;
 1421       }
 1422     }
 1423     gfree(row);
 1424     imgStr->close();
 1425     delete imgStr;
 1426   }
 1427   else { // isMask == true
 1428     int size = (width + 7)/8;
 1429 
 1430     // PDF masks use 0 = draw current color, 1 = leave unchanged.
 1431     // We invert this to provide the standard interpretation of alpha
 1432     // (0 = transparent, 1 = opaque). If the colorMap already inverts
 1433     // the mask we leave the data unchanged.
 1434     int invert_bits = 0xff;
 1435     if (colorMap) {
 1436       GfxGray gray;
 1437       unsigned char zero[gfxColorMaxComps];
 1438       memset(zero, 0, sizeof(zero));
 1439       colorMap->getGray(zero, &gray);
 1440       if (colToByte(gray) == 0)
 1441         invert_bits = 0x00;
 1442     }
 1443 
 1444     str->reset();
 1445     unsigned char *png_row = (unsigned char *)gmalloc(size);
 1446 
 1447     for (int ri = 0; ri < height; ++ri)
 1448     {
 1449       for(int i = 0; i < size; i++)
 1450         png_row[i] = str->getChar() ^ invert_bits;
 1451 
 1452       if (!writer->writeRow( &png_row ))
 1453       {
 1454         error(errIO, -1, "Failed to write into PNG '{0:t}'", fName);
 1455         delete writer;
 1456         fclose(f1);
 1457         gfree(png_row);
 1458         return;
 1459       }
 1460     }
 1461     str->close();
 1462     gfree(png_row);
 1463   }
 1464 
 1465   str->close();
 1466 
 1467   writer->close();
 1468   delete writer;
 1469   fclose(f1);
 1470 
 1471   if (dataUrls) {
 1472     delete fName;
 1473     fName = new GooString(std::string("data:image/png;base64,") + gbase64Encode(ims.getBuffer()));
 1474   }
 1475   pages->addImage(fName, state);
 1476 #else
 1477   return;
 1478 #endif
 1479 }
 1480 
 1481 GooString *HtmlOutputDev::createImageFileName(const char *ext)
 1482 {
 1483   return GooString::format("{0:s}-{1:d}_{2:d}.{3:s}", Docname->c_str(), pageNum, pages->getNumImages() + 1, ext);
 1484 }
 1485 
 1486 void HtmlOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str,
 1487                   int width, int height, bool invert,
 1488                   bool interpolate, bool inlineImg) {
 1489 
 1490   if (ignore||(complexMode && !xml)) {
 1491     OutputDev::drawImageMask(state, ref, str, width, height, invert, interpolate, inlineImg);
 1492     return;
 1493   }
 1494   
 1495   // dump JPEG file
 1496   if (dumpJPEG  && str->getKind() == strDCT) {
 1497     drawJpegImage(state, str);
 1498   }
 1499   else {
 1500 #ifdef ENABLE_LIBPNG
 1501     drawPngImage(state, str, width, height, nullptr, true);
 1502 #else
 1503     OutputDev::drawImageMask(state, ref, str, width, height, invert, interpolate, inlineImg);
 1504 #endif
 1505   }
 1506 }
 1507 
 1508 void HtmlOutputDev::drawImage(GfxState *state, Object *ref, Stream *str,
 1509                   int width, int height, GfxImageColorMap *colorMap,
 1510                   bool interpolate, const int *maskColors, bool inlineImg) {
 1511 
 1512   if (ignore||(complexMode && !xml)) {
 1513     OutputDev::drawImage(state, ref, str, width, height, colorMap, interpolate,
 1514              maskColors, inlineImg);
 1515     return;
 1516   }
 1517   
 1518   /*if( !globalParams->getErrQuiet() )
 1519     printf("image stream of kind %d\n", str->getKind());*/
 1520   // dump JPEG file
 1521   if (dumpJPEG && str->getKind() == strDCT && (colorMap->getNumPixelComps() == 1 ||
 1522       colorMap->getNumPixelComps() == 3) && !inlineImg) {
 1523     drawJpegImage(state, str);
 1524   }
 1525   else {
 1526 #ifdef ENABLE_LIBPNG
 1527     drawPngImage(state, str, width, height, colorMap );
 1528 #else
 1529     OutputDev::drawImage(state, ref, str, width, height, colorMap, interpolate,
 1530                          maskColors, inlineImg);
 1531 #endif
 1532   }
 1533 }
 1534 
 1535 
 1536 
 1537 void HtmlOutputDev::doProcessLink(AnnotLink* link){
 1538   double _x1,_y1,_x2,_y2;
 1539   int x1,y1,x2,y2;
 1540 
 1541   link->getRect(&_x1,&_y1,&_x2,&_y2);
 1542   cvtUserToDev(_x1,_y1,&x1,&y1);
 1543   
 1544   cvtUserToDev(_x2,_y2,&x2,&y2); 
 1545 
 1546 
 1547   GooString* _dest=getLinkDest(link);
 1548   HtmlLink t((double) x1,(double) y2,(double) x2,(double) y1,_dest);
 1549   pages->AddLink(t);
 1550   delete _dest;
 1551 }
 1552 
 1553 GooString* HtmlOutputDev::getLinkDest(AnnotLink *link){
 1554   if (!link->getAction())
 1555     return new GooString();
 1556   switch(link->getAction()->getKind()) 
 1557   {
 1558       case actionGoTo:
 1559       {
 1560       GooString* file = new GooString(gbasename(Docname->c_str()));
 1561       int destPage=1;
 1562       LinkGoTo *ha=(LinkGoTo *)link->getAction();
 1563       LinkDest *dest=nullptr;
 1564       if (ha->getDest()!=nullptr)
 1565           dest=ha->getDest()->copy();
 1566       else if (ha->getNamedDest()!=nullptr)
 1567           dest=catalog->findDest(ha->getNamedDest());
 1568           
 1569       if (dest){ 
 1570           if (dest->isPageRef()){
 1571           const Ref pageref=dest->getPageRef();
 1572           destPage=catalog->findPage(pageref);
 1573           }
 1574           else {
 1575           destPage=dest->getPageNum();
 1576           }
 1577 
 1578           delete dest;
 1579 
 1580           GooString *str=GooString::fromInt(destPage);
 1581           /*        complex     simple
 1582             frames      file-4.html files.html#4
 1583         noframes    file.html#4 file.html#4
 1584            */
 1585           if (noframes)
 1586           {
 1587           file->append(".html#");
 1588           file->append(str);
 1589           }
 1590           else
 1591           {
 1592             if( complexMode ) 
 1593         {
 1594             file->append("-");
 1595             file->append(str);
 1596             file->append(".html");
 1597         }
 1598         else
 1599         {
 1600             file->append("s.html#");
 1601             file->append(str);
 1602         }
 1603           }
 1604 
 1605           if (printCommands) printf(" link to page %d ",destPage);
 1606           delete str;
 1607           return file;
 1608       }
 1609       else 
 1610       {
 1611           return new GooString();
 1612       }
 1613       }
 1614       case actionGoToR:
 1615       {
 1616       LinkGoToR *ha=(LinkGoToR *) link->getAction();
 1617       LinkDest *dest=nullptr;
 1618       int destPage=1;
 1619       GooString *file=new GooString();
 1620       if (ha->getFileName()){
 1621           delete file;
 1622           file=new GooString(ha->getFileName()->c_str());
 1623       }
 1624       if (ha->getDest()!=nullptr)  dest=ha->getDest()->copy();
 1625       if (dest&&file){
 1626           if (!(dest->isPageRef()))  destPage=dest->getPageNum();
 1627           delete dest;
 1628 
 1629           if (printCommands) printf(" link to page %d ",destPage);
 1630           if (printHtml){
 1631           const char *p=file->c_str()+file->getLength()-4;
 1632           if (!strcmp(p, ".pdf") || !strcmp(p, ".PDF")){
 1633               file->del(file->getLength()-4,4);
 1634               file->append(".html");
 1635           }
 1636           file->append('#');
 1637           GooString *pgNum = GooString::fromInt(destPage);
 1638           file->append(pgNum);
 1639           delete pgNum;
 1640           }
 1641       }
 1642       if (printCommands && file) printf("filename %s\n",file->c_str());
 1643       return file;
 1644       }
 1645       case actionURI:
 1646       { 
 1647       LinkURI *ha=(LinkURI *) link->getAction();
 1648       GooString* file=new GooString(ha->getURI()->c_str());
 1649       // printf("uri : %s\n",file->c_str());
 1650       return file;
 1651       }
 1652       case actionLaunch:
 1653       if (printHtml) {
 1654           LinkLaunch *ha=(LinkLaunch *) link->getAction();
 1655           GooString* file=new GooString(ha->getFileName()->c_str());
 1656           const char *p=file->c_str()+file->getLength()-4;
 1657           if (!strcmp(p, ".pdf") || !strcmp(p, ".PDF")){
 1658           file->del(file->getLength()-4,4);
 1659           file->append(".html");
 1660           }
 1661           if (printCommands) printf("filename %s",file->c_str());
 1662     
 1663           return file;      
 1664   
 1665       }
 1666       // fallthrough
 1667       default:
 1668       return new GooString();
 1669   }
 1670 }
 1671 
 1672 void HtmlOutputDev::dumpMetaVars(FILE *file)
 1673 {
 1674   GooString *var;
 1675 
 1676   for(std::size_t i = 0; i < glMetaVars->size(); i++)
 1677   {
 1678      HtmlMetaVar *t = (*glMetaVars)[i];
 1679      var = t->toString(); 
 1680      fprintf(file, "%s\n", var->c_str());
 1681      delete var;
 1682   }
 1683 }
 1684 
 1685 bool HtmlOutputDev::dumpDocOutline(PDFDoc* doc)
 1686 { 
 1687     FILE * output = nullptr;
 1688     bool bClose = false;
 1689 
 1690     if (!ok)
 1691                 return false;
 1692   
 1693     Outline *outline = doc->getOutline();
 1694     if (!outline)
 1695         return false;
 1696 
 1697     const std::vector<OutlineItem*> *outlines = outline->getItems();
 1698     if (!outlines)
 1699         return false;
 1700   
 1701     if (!complexMode || xml)
 1702     {
 1703         output = page;
 1704     }
 1705     else if (complexMode && !xml)
 1706     {
 1707         if (noframes)
 1708         {
 1709             output = page; 
 1710             fputs("<hr/>\n", output);
 1711         }
 1712         else
 1713         {
 1714             GooString *str = Docname->copy();
 1715             str->append("-outline.html");
 1716             output = fopen(str->c_str(), "w");
 1717             delete str;
 1718             if (output == nullptr)
 1719                 return false;
 1720             bClose = true;
 1721 
 1722             GooString *htmlEncoding =
 1723                 HtmlOutputDev::mapEncodingToHtml(globalParams->getTextEncodingName());
 1724 
 1725             fprintf(output, "<html xmlns=\"http://www.w3.org/1999/xhtml\" " \
 1726                                 "lang=\"\" xml:lang=\"\">\n"            \
 1727                                 "<head>\n"                              \
 1728                                 "<title>Document Outline</title>\n"     \
 1729                                 "<meta http-equiv=\"Content-Type\" content=\"text/html; " \
 1730                                 "charset=%s\"/>\n"                      \
 1731                                 "</head>\n<body>\n", htmlEncoding->c_str());
 1732             delete htmlEncoding;
 1733         }
 1734     }
 1735  
 1736     if (!xml)
 1737     {
 1738         bool done = newHtmlOutlineLevel(output, outlines);
 1739         if (done && !complexMode)
 1740             fputs("<hr/>\n", output);
 1741     
 1742         if (bClose)
 1743         {
 1744             fputs("</body>\n</html>\n", output);
 1745             fclose(output);
 1746         }
 1747     }
 1748     else
 1749         newXmlOutlineLevel(output, outlines);
 1750 
 1751     return true;
 1752 }
 1753 
 1754 bool HtmlOutputDev::newHtmlOutlineLevel(FILE *output, const std::vector<OutlineItem*> *outlines, int level)
 1755 {
 1756     bool atLeastOne = false;
 1757 
 1758     if (level == 1)
 1759     {
 1760         fputs("<a name=\"outline\"></a>", output);
 1761         fputs("<h1>Document Outline</h1>\n", output);
 1762     }
 1763     fputs("<ul>\n",output);
 1764 
 1765     for (std::size_t i = 0; i < outlines->size(); i++)
 1766     {
 1767         OutlineItem *item = (*outlines)[i];
 1768         GooString *titleStr = HtmlFont::HtmlFilter(item->getTitle(),
 1769                                item->getTitleLength());
 1770 
 1771         GooString *linkName = nullptr;;
 1772         const int itemPage = getOutlinePageNum(item);
 1773         if (itemPage > 0)
 1774         {
 1775                 /*      complex     simple
 1776                 frames      file-4.html files.html#4
 1777                 noframes    file.html#4 file.html#4
 1778                 */
 1779                 linkName = new GooString(gbasename(Docname->c_str()));
 1780                 GooString *str=GooString::fromInt(itemPage);
 1781                 if (noframes) {
 1782                     linkName->append(".html#");
 1783                     linkName->append(str);
 1784                 } else {
 1785                     if( complexMode ) {
 1786                         linkName->append("-");
 1787                         linkName->append(str);
 1788                         linkName->append(".html");
 1789                     } else {
 1790                         linkName->append("s.html#");
 1791                         linkName->append(str);
 1792                     }
 1793                 }
 1794                 delete str;
 1795         }
 1796 
 1797         fputs("<li>",output);
 1798         if (linkName)
 1799             fprintf(output,"<a href=\"%s\">", linkName->c_str());
 1800         fputs(titleStr->c_str(),output);
 1801         if (linkName) {
 1802             fputs("</a>",output);
 1803             delete linkName;
 1804         }
 1805         delete titleStr;
 1806         atLeastOne = true;
 1807 
 1808         item->open();
 1809         if (item->hasKids() && item->getKids())
 1810         {
 1811             fputs("\n",output);
 1812             newHtmlOutlineLevel(output, item->getKids(), level+1);
 1813         }
 1814         item->close();
 1815         fputs("</li>\n",output);
 1816     }
 1817     fputs("</ul>\n",output);
 1818 
 1819     return atLeastOne;
 1820 }
 1821 
 1822 void HtmlOutputDev::newXmlOutlineLevel(FILE *output, const std::vector<OutlineItem*> *outlines)
 1823 {
 1824     fputs("<outline>\n", output);
 1825 
 1826     for (std::size_t i = 0; i < outlines->size(); i++)
 1827     {
 1828         OutlineItem *item     = (*outlines)[i];
 1829         GooString   *titleStr = HtmlFont::HtmlFilter(item->getTitle(),
 1830                                                      item->getTitleLength());
 1831         const int itemPage = getOutlinePageNum(item);
 1832         if (itemPage > 0)
 1833         {
 1834             fprintf(output, "<item page=\"%d\">%s</item>\n",
 1835                     itemPage, titleStr->c_str());
 1836         }
 1837         else
 1838         {
 1839             fprintf(output, "<item>%s</item>\n", titleStr->c_str());
 1840         }
 1841         delete titleStr;
 1842 
 1843         item->open();
 1844         if (item->hasKids() && item->getKids())
 1845         {
 1846             newXmlOutlineLevel(output, item->getKids());
 1847         }
 1848         item->close();
 1849     }    
 1850 
 1851     fputs("</outline>\n", output);
 1852 }
 1853 
 1854 int HtmlOutputDev::getOutlinePageNum(OutlineItem *item)
 1855 {
 1856     const LinkAction *action   = item->getAction();
 1857     const LinkGoTo   *link     = nullptr;
 1858     LinkDest   *linkdest = nullptr;
 1859     int         pagenum  = -1;
 1860 
 1861     if (!action || action->getKind() != actionGoTo)
 1862         return pagenum;
 1863 
 1864     link = dynamic_cast<const LinkGoTo*>(action);
 1865 
 1866     if (!link || !link->isOk())
 1867         return pagenum;
 1868 
 1869     if (link->getDest())
 1870         linkdest = link->getDest()->copy();
 1871     else if (link->getNamedDest())
 1872         linkdest = catalog->findDest(link->getNamedDest());
 1873 
 1874     if (!linkdest)
 1875         return pagenum;
 1876 
 1877     if (linkdest->isPageRef()) {
 1878         const Ref pageref = linkdest->getPageRef();
 1879         pagenum = catalog->findPage(pageref);
 1880     } else {
 1881         pagenum = linkdest->getPageNum();
 1882     }
 1883 
 1884     delete linkdest;
 1885     return pagenum;
 1886 }