fixed some error messages.
[swftools.git] / pdf2swf / SWFOutputDev.cc
index 4ee30d7..8226c66 100644 (file)
@@ -58,6 +58,7 @@
 #else
 #include "FoFiType1C.h"
 #include "FoFiTrueType.h"
+#include "GHash.h"
 #endif
 #include "SWFOutputDev.h"
 
@@ -92,12 +93,6 @@ static int fontnum = 0;
 
 static int config_use_fontconfig = 1;
 
-// swf <-> pdf pages
-// TODO: move into pdf_doc_t
-static int*pages = 0;
-static int pagebuflen = 0;
-static int pagepos = 0;
-
 /* config */
 static double caplinewidth = 3.0;
 static double zoom = 72; /* xpdf: 86 */
@@ -106,6 +101,8 @@ static int forceType0Fonts = 1;
 static void printInfoString(Dict *infoDict, char *key, char *fmt);
 static void printInfoDate(Dict *infoDict, char *key, char *fmt);
 
+static char* lastfontdir = 0;
+
 struct mapping {
     char*pdffont;
     char*filename;
@@ -143,6 +140,8 @@ typedef struct _fontlist
     _fontlist*next;
 } fontlist_t;
 
+class InfoOutputDev;
+
 class SWFOutputDev:  public OutputDev {
 public:
   gfxdevice_t* output;
@@ -155,6 +154,8 @@ public:
 
   void setMove(int x,int y);
   void setClip(int x1,int y1,int x2,int y2);
+
+  void setInfo(InfoOutputDev*info) {this->info = info;}
   
   int save(char*filename);
     
@@ -164,9 +165,7 @@ public:
   virtual void startPage(int pageNum, GfxState *state, double x1, double y1, double x2, double y2) ;
 
   void endframe();
-  void* getSWF();
-
-  void getDimensions(int*x1,int*y1,int*x2,int*y2);
+  void* get(char*name);
 
   //----- get info about output device
 
@@ -248,7 +247,7 @@ public:
   void drawGeneralImage(GfxState *state, Object *ref, Stream *str,
                                   int width, int height, GfxImageColorMap*colorMap, GBool invert,
                                   GBool inlineImg, int mask, int *maskColors);
-  int SWFOutputDev::setGfxFont(char*id, char*filename);
+  int SWFOutputDev::setGfxFont(char*id, char*filename, double quality);
   void strokeGfxline(GfxState *state, gfxline_t*line);
   void clipToGfxLine(GfxState *state, gfxline_t*line);
   void fillGfxLine(GfxState *state, gfxline_t*line);
@@ -259,6 +258,7 @@ public:
 
   char outer_clip_box; //whether the page clip box is still on
 
+  InfoOutputDev*info;
   SWFOutputState states[64];
   int statepos;
 
@@ -271,6 +271,7 @@ public:
   char* substituteFont(GfxFont*gfxFont, char*oldname);
   char* writeEmbeddedFontToFile(XRef*ref, GfxFont*font);
   int t1id;
+  int textmodeinfo; // did we write "Text will be rendered as polygon" yet?
   int jpeginfo; // did we write "File contains jpegs" yet?
   int pbminfo; // did we write "File contains jpegs" yet?
   int linkinfo; // did we write "File contains links" yet?
@@ -297,12 +298,71 @@ public:
   gfxmatrix_t current_font_matrix;
 
   fontlist_t* fontlist;
+
+  int*pages;
+  int pagebuflen;
+  int pagepos;
+
+  friend void swf_output_preparepage(swf_output_t*swf, int pdfpage, int outputpage);
+};
+
+typedef struct _drawnchar
+{
+    gfxcoord_t x,y;
+    int charid;
+    gfxcolor_t color;
+} drawnchar_t;
+
+class CharBuffer
+{
+    drawnchar_t * chars;
+    int buf_size;
+    int num_chars;
+
+public:
+
+    CharBuffer()
+    {
+       buf_size = 32;
+       chars = (drawnchar_t*)malloc(sizeof(drawnchar_t)*buf_size);
+       memset(chars, 0, sizeof(drawnchar_t)*buf_size);
+       num_chars = 0;
+    }
+    ~CharBuffer()
+    {
+       free(chars);chars = 0;
+    }
+
+    void grow(int size)
+    {
+       if(size>=buf_size) {
+           buf_size += 32;
+           chars = (drawnchar_t*)realloc(chars, sizeof(drawnchar_t)*buf_size);
+       }
+    }
+
+    void addChar(int charid, gfxcoord_t x, gfxcoord_t y, gfxcolor_t color)
+    {
+       grow(num_chars);
+       chars[num_chars].x = x;
+       chars[num_chars].y = y;
+       chars[num_chars].color = color;
+       chars[num_chars].charid = charid;
+    }
 };
 
 static char*getFontID(GfxFont*font);
 
+struct FontInfo
+{
+    GfxFont*font;
+    double max_size;
+};
+
 class InfoOutputDev:  public OutputDev 
 {
+  GHash* id2font;
+  FontInfo* currentfont;
   public:
   int x1,y1,x2,y2;
   int num_links;
@@ -314,9 +374,11 @@ class InfoOutputDev:  public OutputDev
       num_links = 0;
       num_images = 0;
       num_fonts = 0;
+      id2font = new GHash();
   }
   virtual ~InfoOutputDev() 
   {
+      delete id2font;
   }
   virtual GBool upsideDown() {return gTrue;}
   virtual GBool useDrawChar() {return gTrue;}
@@ -338,14 +400,53 @@ class InfoOutputDev:  public OutputDev
   {
       num_links++;
   }
+  virtual double getMaximumFontSize(char*id)
+  {
+      FontInfo*info = (FontInfo*)id2font->lookup(id);
+      if(!info) {
+         msg("<error> Unknown font id: %s", id);
+         return 0.0;
+      }
+      return info->max_size;
+  }
+
   virtual void updateFont(GfxState *state) 
   {
       GfxFont*font = state->getFont();
       if(!font)
           return;
-      /*char*id = getFontID(font);*/
-      /* FIXME*/
-      num_fonts++;
+      char*id = getFontID(font);
+      
+      FontInfo*info = (FontInfo*)id2font->lookup(id);
+      if(!info) {
+       GString* idStr = new GString(id);
+       info = new FontInfo;
+       info->font = font;
+       info->max_size = 0;
+       id2font->add(idStr, (void*)info);
+       free(id);
+       num_fonts++;
+      }
+      currentfont = info;
+  }
+  virtual void drawChar(GfxState *state, double x, double y,
+                       double dx, double dy,
+                       double originX, double originY,
+                       CharCode code, Unicode *u, int uLen)
+  {
+      int render = state->getRender();
+      if (render == 3)
+         return;
+      double m11,m21,m12,m22;
+      state->getFontTransMat(&m11, &m12, &m21, &m22);
+      m11 *= state->getHorizScaling();
+      m21 *= state->getHorizScaling();
+      double lenx = sqrt(m11*m11 + m12*m12);
+      double leny = sqrt(m21*m21 + m22*m22);
+      double len = lenx>leny?lenx:leny;
+      if(currentfont && currentfont->max_size < len) {
+         currentfont->max_size = len;
+      }
   }
   virtual void drawImageMask(GfxState *state, Object *ref, Stream *str,
                             int width, int height, GBool invert,
@@ -364,6 +465,7 @@ class InfoOutputDev:  public OutputDev
 SWFOutputDev::SWFOutputDev()
 {
     jpeginfo = 0;
+    textmodeinfo = 0;
     ttfinfo = 0;
     linkinfo = 0;
     pbminfo = 0;
@@ -383,6 +485,9 @@ SWFOutputDev::SWFOutputDev()
     fontlist = 0;
     result = 0;
     outer_clip_box = 0;
+    pages = 0;
+    pagebuflen = 0;
+    pagepos = 0;
     output = (gfxdevice_t*)malloc(sizeof(gfxdevice_t));
     gfxdevice_swf_init(output);
     /* configure device */
@@ -410,18 +515,6 @@ void SWFOutputDev::setClip(int x1,int y1,int x2,int y2)
     this->user_clipy2 = y2;
 }
 
-void SWFOutputDev::getDimensions(int*x1,int*y1,int*x2,int*y2)
-{
-    if(result) {
-       *x1 = (int)result->get(result, "xmin");
-       *y1 = (int)result->get(result, "ymin");
-       *x2 = (int)result->get(result, "xmax");
-       *y2 = (int)result->get(result, "ymax");
-    } else {
-       *x1 = *y1 = *x2 = *y2 = 0;
-    }
-}
-
 static char*getFontID(GfxFont*font)
 {
     GString*gstr = font->getName();
@@ -715,7 +808,7 @@ gfxline_t* gfxPath_to_gfxline(GfxState*state, GfxPath*path, int closed, int user
                if(cpos==0) {
                    draw.lineTo(&draw, x,y);
                } else {
-                   gfxdraw_cubicTo(&draw, bx,by, cx,cy, x,y);
+                   gfxdraw_cubicTo(&draw, bx,by, cx,cy, x,y, 0.05);
                }
                needsfix = 1;
                cpos = 0;
@@ -929,10 +1022,10 @@ int SWFOutputDev::save(char*filename)
     finish();
     return result->save(result, filename);
 }
-void* SWFOutputDev::getSWF()
+void* SWFOutputDev::get(char*name)
 {
     finish();
-    return result->get(result, "swf");
+    return result->get(result, name);
 }
 
 SWFOutputDev::~SWFOutputDev() 
@@ -943,6 +1036,10 @@ SWFOutputDev::~SWFOutputDev()
        this->result->destroy(this->result);
        this->result = 0;
     }
+    
+    if(this->pages) {
+       free(this->pages); this->pages = 0;
+    }
 
     fontlist_t*l = this->fontlist;
     while(l) {
@@ -1036,6 +1133,11 @@ int getGfxCharID(gfxfont_t*font, int charnr, char *charname, int u)
        return font->unicode2glyph[u];
     }
 
+    /* we don't need to "draw" space characters, so don't overdo the search
+       for a matching glyph */
+    if(charname && !strcasecmp(charname, "space"))
+       return -1;
+
     if(charnr>=0 && charnr<font->num_glyphs) {
        msg("<debug> Char [>%d<,%s,%d] maps to %d\n", charnr, charname, u, charnr);
        return charnr;
@@ -1080,8 +1182,10 @@ void SWFOutputDev::drawChar(GfxState *state, double x, double y,
 {
     int render = state->getRender();
     // check for invisible text -- this is used by Acrobat Capture
-    if (render == 3)
+    if (render == 3) {
+       msg("<debug> Ignoring invisible text: char %d at %f,%f", c, x, y);
        return;
+    }
 
     if(states[statepos].textRender != render)
        msg("<error> Internal error: drawChar.render!=beginString.render");
@@ -1125,8 +1229,9 @@ void SWFOutputDev::drawChar(GfxState *state, double x, double y,
        Gfx8BitFont*font8;
        font8 = (Gfx8BitFont*)font;
        char**enc=font8->getEncoding();
-       if(enc && enc[c])
+       if(enc && enc[c] && strcasecmp(enc[c], "space")) {
           name = enc[c];
+       }
     }
     if (CIDToGIDMap) {
        msg("<debug> drawChar(%f, %f, c='%c' (%d), GID=%d, u=%d <%d>) CID=%d name=\"%s\" render=%d\n", x, y, (c&127)>=32?c:'?', c, CIDToGIDMap[c], u, uLen, font->isCIDFont(), FIXNULL(name), render);
@@ -1135,10 +1240,30 @@ void SWFOutputDev::drawChar(GfxState *state, double x, double y,
        msg("<debug> drawChar(%f,%f,c='%c' (%d), u=%d <%d>) CID=%d name=\"%s\" render=%d\n",x,y,(c&127)>=32?c:'?',c,u, uLen, font->isCIDFont(), FIXNULL(name), render);
     }
 
-    int charid = getGfxCharID(current_gfxfont, c, name, u);
-    if(charid<0) {
-       msg("<warning> Didn't find character '%s' (c=%d,u=%d) in current charset (%s, %d characters)", 
-               FIXNULL(name),c, u, FIXNULL((char*)current_font_id), current_gfxfont->num_glyphs);
+    int charid = -1;
+   
+    if(uLen<=1) {
+       charid = getGfxCharID(current_gfxfont, c, name, u);
+    } else {
+       charid = getGfxCharID(current_gfxfont, c, 0, -1);
+       if(charid < 0) {
+           /* multiple unicodes- should usually map to a ligature.
+              if the ligature doesn't exist, we need to draw
+              the characters one-by-one. */
+           int t;
+           msg("<warning> ligature %d missing in font %s\n", c, current_font_id);
+           for(t=0;t<uLen;t++) {
+               drawChar(state, x, y, dx, dy, originX, originY, c, _u+t, 1);
+           }
+           return;
+       }
+    }
+
+    if(charid<0 && name) {
+       if(strcasecmp(name, "space")) {
+           msg("<warning> Didn't find character '%s' (c=%d,u=%d) in current charset (%s, %d characters)", 
+                   FIXNULL(name),c, u, FIXNULL((char*)current_font_id), current_gfxfont->num_glyphs);
+       }
        return;
     }
 
@@ -1151,6 +1276,10 @@ void SWFOutputDev::drawChar(GfxState *state, double x, double y,
        output->drawchar(output, current_font_id, charid, &col, &m);
     } else {
        msg("<debug> Drawing glyph %d as shape", charid);
+       if(!textmodeinfo) {
+           msg("<notice> Some texts will be rendered as shape");
+           textmodeinfo = 1;
+       }
        gfxline_t*glyph = current_gfxfont->glyphs[charid].line;
        gfxline_t*tglyph = gfxline_clone(glyph);
        gfxline_transform(tglyph, &m);
@@ -1398,7 +1527,8 @@ void SWFOutputDev::drawLink(Link *link, Catalog *catalog)
                     }
                     else if(strstr(s, "last") || strstr(s, "end"))
                     {
-                        page = pagepos>0?pages[pagepos-1]:0;
+                       if(pages && pagepos>0)
+                           page = pages[pagepos-1];
                     }
                     else if(strstr(s, "first") || strstr(s, "top"))
                     {
@@ -1449,13 +1579,19 @@ void SWFOutputDev::drawLink(Link *link, Catalog *catalog)
     if(page>0)
     {
         int t;
-        for(t=0;t<pagepos;t++)
-            if(pages[t]==page)
+       int lpage = -1;
+        for(t=1;t<=pagepos;t++) {
+            if(pages[t]==page) {
+               lpage = t;
                 break;
-        if(t!=pagepos) {
+           }
+       }
+        if(lpage>=0) {
            char buf[80];
            sprintf(buf, "page%d", t);
             output->drawlink(output, points, buf);
+       } else {
+           msg("<warning> Invalid link to page %d", page);
        }
     }
     else if(url)
@@ -1788,7 +1924,7 @@ char* searchForSuitableFont(GfxFont*gfxFont)
 char* SWFOutputDev::substituteFont(GfxFont*gfxFont, char* oldname)
 {
     char*fontname = 0, *filename = 0;
-    msg("<notice> subsituteFont(%s)", oldname);
+    msg("<notice> substituteFont(%s)", oldname);
 
     if(!(fontname = searchForSuitableFont(gfxFont))) {
        fontname = "Times-Roman";
@@ -1847,7 +1983,7 @@ void SWFOutputDev::setXRef(PDFDoc*doc, XRef *xref)
     this->xref = xref;
 }
 
-int SWFOutputDev::setGfxFont(char*id, char*filename)
+int SWFOutputDev::setGfxFont(char*id, char*filename, double maxSize)
 {
     gfxfont_t*font = 0;
     fontlist_t*last=0,*l = this->fontlist;
@@ -1865,7 +2001,14 @@ int SWFOutputDev::setGfxFont(char*id, char*filename)
        l = l->next;
     }
     if(!filename) return 0;
-    font = gfxfont_load(filename);
+
+    /* A font size of e.g. 9 means the font will be scaled down by
+       1024 and scaled up by 9. So to have a maximum error of 1/20px,
+       we have to divide 0.05 by (fontsize/1024)
+     */
+    double quality = (1024 * 0.05) / maxSize;
+    
+    font = gfxfont_load(filename, quality);
     l = new fontlist_t;
     l->font = font;
     l->filename = strdup(filename);
@@ -1890,6 +2033,11 @@ void SWFOutputDev::updateFont(GfxState *state)
        return;
     }  
     char * fontid = getFontID(gfxFont);
+    double maxSize = 1.0;
+
+    if(this->info) {
+       maxSize = this->info->getMaximumFontSize(fontid);
+    }
     
     int t;
     /* first, look if we substituted this font before-
@@ -1905,7 +2053,7 @@ void SWFOutputDev::updateFont(GfxState *state)
 
     /* second, see if this is a font which was used before-
        if so, we are done */
-    if(setGfxFont(fontid, 0)) {
+    if(setGfxFont(fontid, 0, 0)) {
        free(fontid);
        return;
     }
@@ -1953,7 +2101,12 @@ void SWFOutputDev::updateFont(GfxState *state)
     if(!fileName) {
        char * fontname = getFontName(gfxFont);
        msg("<warning> Font %s %scould not be loaded.", fontname, embedded?"":"(not embedded) ");
-       msg("<warning> Try putting a TTF version of that font (named \"%s.ttf\") into /swftools/fonts", fontname);
+       
+       if(lastfontdir)
+           msg("<warning> Try putting a TTF version of that font (named \"%s.ttf\") into %s/swftools/fonts", fontname, lastfontdir);
+       else
+           msg("<warning> Try specifying one or more font directories");
+
        fileName = substituteFont(gfxFont, fontid);
        if(fontid) { free(fontid);fontid = strdup(substitutetarget[substitutepos-1]); /*ugly hack*/};
        msg("<notice> Font is now %s (%s)", fontid, fileName);
@@ -1965,13 +2118,13 @@ void SWFOutputDev::updateFont(GfxState *state)
        return;
     }
        
-    msg("<verbose> updateFont(%s) -> %s", fontid, fileName);
+    msg("<verbose> updateFont(%s) -> %s (max size: %f)", fontid, fileName, maxSize);
     dumpFontInfo("<verbose>", gfxFont);
 
     //swfoutput_setfont(&output, fontid, fileName);
     
-    if(!setGfxFont(fontid, 0)) {
-       setGfxFont(fontid, fileName);
+    if(!setGfxFont(fontid, 0, 0)) {
+       setGfxFont(fontid, fileName, maxSize);
     }
    
     if(fileName && del)
@@ -2466,6 +2619,7 @@ void pdfswf_addfontdir(char*dirname)
 {
 #ifdef HAVE_DIRENT_H
     msg("<notice> Adding %s to font directories", dirname);
+    lastfontdir = strdup(dirname);
     DIR*dir = opendir(dirname);
     if(!dir) {
        msg("<warning> Couldn't open directory %s\n", dirname);
@@ -2510,6 +2664,7 @@ typedef struct _pdf_doc_internal
 {
     int protect;
     PDFDoc*doc;
+    InfoOutputDev*info;
 } pdf_doc_internal_t;
 typedef struct _pdf_page_internal
 {
@@ -2586,24 +2741,19 @@ pdf_doc_t* pdf_init(char*filename, char*userPassword)
           if(!i->doc->okToChange() || !i->doc->okToAddNotes())
               i->protect = 1;
     }
-   
-    return pdf_doc;
-}
 
-static void pdfswf_preparepage(int page)
-{
-    /*FIXME*/
-    if(!pages) {
-       pages = (int*)malloc(1024*sizeof(int));
-       pagebuflen = 1024;
-    } else {
-       if(pagepos == pagebuflen)
-       {
-           pagebuflen+=1024;
-           pages = (int*)realloc(pages, pagebuflen);
-       }
+    InfoOutputDev*io = new InfoOutputDev();
+    int t;
+    for(t=1;t<=pdf_doc->num_pages;t++) {
+#ifdef XPDF_101
+       i->doc->displayPage((OutputDev*)io, t, /*zoom*/zoom, /*rotate*/0, /*doLinks*/(int)1);
+#else
+       i->doc->displayPage((OutputDev*)io, t, zoom, zoom, /*rotate*/0, true, /*doLinks*/(int)1);
+#endif
     }
-    pages[pagepos++] = page;
+    i->info = io;
+
+    return pdf_doc;
 }
 
 class MemCheck
@@ -2622,7 +2772,9 @@ void pdf_destroy(pdf_doc_t*pdf_doc)
 
     delete i->doc; i->doc=0;
     
-    free(pages); pages = 0; //FIXME
+    if(i->info) {
+       delete i->info;i->info=0;
+    }
 
     free(pdf_doc->internal);pdf_doc->internal=0;
     free(pdf_doc);pdf_doc=0;
@@ -2673,29 +2825,51 @@ void swf_output_startframe(swf_output_t*swf, int width, int height)
 {
     swf_output_internal_t*i= (swf_output_internal_t*)swf->internal;
     i->outputDev->startFrame(width, height);
-    i->outputDev->getDimensions(&swf->x1, &swf->y1, &swf->x2, &swf->y2);
 }
 
 void swf_output_endframe(swf_output_t*swf)
 {
     swf_output_internal_t*i= (swf_output_internal_t*)swf->internal;
     i->outputDev->endframe();
-    i->outputDev->getDimensions(&swf->x1, &swf->y1, &swf->x2, &swf->y2);
+}
+
+void swf_output_preparepage(swf_output_t*swf, int pdfpage, int outputpage)
+{
+    swf_output_internal_t*i= (swf_output_internal_t*)swf->internal;
+    SWFOutputDev*o = i->outputDev;
+
+    if(pdfpage < 0)
+       return;
+
+    if(!o->pages) {
+       o->pagebuflen = 1024;
+       o->pages = (int*)malloc(o->pagebuflen*sizeof(int));
+       memset(o->pages, -1, o->pagebuflen*sizeof(int));
+    } else {
+       while(pdfpage >= o->pagebuflen)
+       {
+           int oldlen = o->pagebuflen;
+           o->pagebuflen+=1024;
+           o->pages = (int*)realloc(o->pages, o->pagebuflen*sizeof(int));
+           memset(&o->pages[oldlen], -1, (o->pagebuflen-oldlen)*sizeof(int));
+       }
+    }
+    o->pages[pdfpage] = outputpage;
+    if(pdfpage>o->pagepos)
+       o->pagepos = pdfpage;
 }
 
 int swf_output_save(swf_output_t*swf, char*filename)
 {
     swf_output_internal_t*i= (swf_output_internal_t*)swf->internal;
     int ret = i->outputDev->save(filename);
-    i->outputDev->getDimensions(&swf->x1, &swf->y1, &swf->x2, &swf->y2);
     return ret;
 }
 
-void* swf_output_get(swf_output_t*swf)
+void* swf_output_get(swf_output_t*swf,char*name)
 {
     swf_output_internal_t*i= (swf_output_internal_t*)swf->internal;
-    void* ret = i->outputDev->getSWF();
-    i->outputDev->getDimensions(&swf->x1, &swf->y1, &swf->x2, &swf->y2);
+    void* ret = i->outputDev->get(name);
     return ret;
 }
 
@@ -2712,17 +2886,22 @@ void pdf_page_render2(pdf_page_t*page, swf_output_t*swf)
     pdf_doc_internal_t*pi = (pdf_doc_internal_t*)page->parent->internal;
     swf_output_internal_t*si = (swf_output_internal_t*)swf->internal;
 
+    if(!pi) {
+       msg("<fatal> pdf_page_render: Parent PDF this page belongs to doesn't exist yet/anymore");
+       return;
+    }
+
     if(pi->protect) {
        gfxdevice_t*dev = si->outputDev->output;
         dev->setparameter(dev, "protect", "1");
     }
+    si->outputDev->setInfo(pi->info);
     si->outputDev->setXRef(pi->doc, pi->doc->getXRef());
 #ifdef XPDF_101
     pi->doc->displayPage((OutputDev*)si->outputDev, page->nr, /*zoom*/zoom, /*rotate*/0, /*doLinks*/(int)1);
 #else
     pi->doc->displayPage((OutputDev*)si->outputDev, page->nr, zoom, zoom, /*rotate*/0, true, /*doLinks*/(int)1);
 #endif
-    si->outputDev->getDimensions(&swf->x1, &swf->y1, &swf->x2, &swf->y2);
 }
 
 void pdf_page_rendersection(pdf_page_t*page, swf_output_t*output, int x, int y, int x1, int y1, int x2, int y2)
@@ -2779,4 +2958,5 @@ pdf_page_info_t* pdf_page_getinfo(pdf_page_t*page)
 void pdf_page_info_destroy(pdf_page_info_t*info)
 {
     free(info);
+
 }