zoom is now a double
[swftools.git] / pdf2swf / SWFOutputDev.cc
index fd753d5..0769800 100644 (file)
@@ -29,7 +29,7 @@
 #ifdef HAVE_SYS_STAT_H
 #include <sys/stat.h>
 #endif
-#ifdef HAVE_FONTCONFIG_H
+#ifdef HAVE_FONTCONFIG
 #include <fontconfig.h>
 #endif
 //xpdf header files
@@ -65,7 +65,8 @@
 #include "swfoutput.h"
 #include "../lib/log.h"
 #include "../lib/gfxdevice.h"
-#include "gfxtools.h"
+#include "../lib/gfxtools.h"
+#include "../lib/gfxfont.h"
 
 #include <math.h>
 
@@ -89,7 +90,7 @@ static int pagepos = 0;
 
 /* config */
 static double caplinewidth = 3.0;
-static int zoom = 72; /* xpdf: 86 */
+static double zoom = 72; /* xpdf: 86 */
 static int forceType0Fonts = 1;
 
 static void printInfoString(Dict *infoDict, char *key, char *fmt);
@@ -124,6 +125,14 @@ class SWFOutputState {
     }
 };
 
+typedef struct _fontlist
+{
+    char*id;
+    char*filename;
+    gfxfont_t*font;
+    _fontlist*next;
+} fontlist_t;
+
 class SWFOutputDev:  public OutputDev {
   int outputstarted;
   struct swfoutput output;
@@ -227,6 +236,11 @@ 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);
+  void strokeGfxline(GfxState *state, gfxline_t*line);
+  void clipToGfxLine(GfxState *state, gfxline_t*line);
+  void fillGfxLine(GfxState *state, gfxline_t*line);
+
   SWFOutputState states[64];
   int statepos;
 
@@ -257,6 +271,14 @@ public:
 
   int user_movex,user_movey;
   int user_clipx1,user_clipx2,user_clipy1,user_clipy2;
+
+  gfxline_t* current_text_stroke;
+  gfxline_t* current_text_clip;
+  char* current_font_id;
+  gfxfont_t* current_gfxfont;
+  gfxmatrix_t current_font_matrix;
+
+  fontlist_t* fontlist;
 };
 
 static char*getFontID(GfxFont*font);
@@ -339,6 +361,9 @@ SWFOutputDev::SWFOutputDev()
     user_clipy1 = 0;
     user_clipx2 = 0;
     user_clipy2 = 0;
+    current_text_stroke = 0;
+    current_text_clip = 0;
+    fontlist = 0;
     memset(&output, 0, sizeof(output));
 //    printf("SWFOutputDev::SWFOutputDev() \n");
 };
@@ -675,6 +700,13 @@ gfxline_t* gfxPath_to_gfxline(GfxState*state, GfxPath*path, int closed)
 void SWFOutputDev::stroke(GfxState *state) 
 {
     GfxPath * path = state->getPath();
+    gfxline_t*line= gfxPath_to_gfxline(state, path, 0);
+    strokeGfxline(state, line);
+    gfxline_free(line);
+}
+
+void SWFOutputDev::strokeGfxline(GfxState *state, gfxline_t*line)
+{
     int lineCap = state->getLineCap(); // 0=butt, 1=round 2=square
     int lineJoin = state->getLineJoin(); // 0=miter, 1=round 2=bevel
     double miterLimit = state->getMiterLimit();
@@ -702,13 +734,13 @@ void SWFOutputDev::stroke(GfxState *state)
     else if(lineJoin == 1) joinType = gfx_joinRound;
     else if(lineJoin == 2) joinType = gfx_joinBevel;
 
-    gfxline_t*line= gfxPath_to_gfxline(state, path, 0);
-    
     int dashnum = 0;
     double dashphase = 0;
     double * ldash = 0;
     state->getLineDash(&ldash, &dashnum, &dashphase);
 
+    gfxline_t*line2 = 0;
+
     if(dashnum && ldash) {
        float * dash = (float*)malloc(sizeof(float)*(dashnum+1));
        int t;
@@ -725,8 +757,7 @@ void SWFOutputDev::stroke(GfxState *state)
            dump_outline(line);
        }
 
-       gfxline_t*line2 = gfxtool_dash_line(line, dash, dashphase);
-       gfxline_free(line);
+       line2 = gfxtool_dash_line(line, dash, dashphase);
        line = line2;
        msg("<trace> After dashing:");
     }
@@ -746,41 +777,45 @@ void SWFOutputDev::stroke(GfxState *state)
     }
    
     swfoutput_drawgfxline(&output, line, width, &col, capType, joinType, miterLimit);
-    gfxline_free(line);
+    
+    if(line2)
+       gfxline_free(line2);
 }
-void SWFOutputDev::fill(GfxState *state) 
+
+gfxcolor_t getFillColor(GfxState * state)
 {
-    GfxPath * path = state->getPath();
-    double opaq = state->getFillOpacity();
     GfxRGB rgb;
+    double opaq = state->getFillOpacity();
     state->getFillRGB(&rgb);
     gfxcolor_t col;
     col.r = (unsigned char)(rgb.r*255);
     col.g = (unsigned char)(rgb.g*255);
     col.b = (unsigned char)(rgb.b*255);
     col.a = (unsigned char)(opaq*255);
+    return col;
+}
 
-    gfxline_t*line= gfxPath_to_gfxline(state, path, 1);
+void SWFOutputDev::fillGfxLine(GfxState *state, gfxline_t*line) 
+{
+    gfxcolor_t col = getFillColor(state);
 
     if(getLogLevel() >= LOGLEVEL_TRACE)  {
         msg("<trace> fill %02x%02x%02x%02x\n", col.r, col.g, col.b, col.a);
         dump_outline(line);
     }
-
     swfoutput_fillgfxline(&output, line, &col);
+}
+void SWFOutputDev::fill(GfxState *state) 
+{
+    GfxPath * path = state->getPath();
+    gfxline_t*line= gfxPath_to_gfxline(state, path, 1);
+    fillGfxLine(state, line);
     gfxline_free(line);
 }
 void SWFOutputDev::eoFill(GfxState *state) 
 {
     GfxPath * path = state->getPath();
-    double opaq = state->getFillOpacity();
-    GfxRGB rgb;
-    state->getFillRGB(&rgb);
-    gfxcolor_t col;
-    col.r = (unsigned char)(rgb.r*255);
-    col.g = (unsigned char)(rgb.g*255);
-    col.b = (unsigned char)(rgb.b*255);
-    col.a = (unsigned char)(opaq*255);
+    gfxcolor_t col = getFillColor(state);
 
     gfxline_t*line= gfxPath_to_gfxline(state, path, 1);
 
@@ -792,11 +827,17 @@ void SWFOutputDev::eoFill(GfxState *state)
     swfoutput_fillgfxline(&output, line, &col);
     gfxline_free(line);
 }
+
 void SWFOutputDev::clip(GfxState *state) 
 {
     GfxPath * path = state->getPath();
     gfxline_t*line = gfxPath_to_gfxline(state, path, 1);
+    clipToGfxLine(state, line);
+    gfxline_free(line);
+}
 
+void SWFOutputDev::clipToGfxLine(GfxState *state, gfxline_t*line) 
+{
     if(getLogLevel() >= LOGLEVEL_TRACE)  {
         msg("<trace> clip\n");
         dump_outline(line);
@@ -804,7 +845,6 @@ void SWFOutputDev::clip(GfxState *state)
 
     swfoutput_startclip(&output, line);
     states[statepos].clipping++;
-    gfxline_free(line);
 }
 void SWFOutputDev::eoClip(GfxState *state) 
 {
@@ -839,6 +879,17 @@ SWFOutputDev::~SWFOutputDev()
 {
     swfoutput_destroy(&output);
     outputstarted = 0;
+
+    fontlist_t*l = this->fontlist;
+    while(l) {
+       fontlist_t*next = l->next;
+       l->next = 0;
+       gfxfont_free(l->font);
+       free(l->id);
+       free(l->filename);
+       free(l);
+       l = next;
+    }
 };
 GBool SWFOutputDev::upsideDown() 
 {
@@ -862,6 +913,12 @@ GBool SWFOutputDev::useGradients()
 char*renderModeDesc[]= {"fill", "stroke", "fill+stroke", "invisible",
                       "clip+fill", "stroke+clip", "fill+stroke+clip", "clip"};
 
+#define RENDER_FILL 0
+#define RENDER_STROKE 1
+#define RENDER_FILLSTROKE 2
+#define RENDER_INVISIBLE 3
+#define RENDER_CLIP 4
+
 static char tmp_printstr[4096];
 char* makeStringPrintable(char*str)
 {
@@ -888,30 +945,75 @@ char* makeStringPrintable(char*str)
     return tmp_printstr;
 }
 
+
+int getGfxCharID(gfxfont_t*font, int charnr, char *charname, int u)
+{
+    int t;
+    if(charname) {
+       for(t=0;t<font->num_glyphs;t++) {
+           if(font->glyphs[t].name && !strcmp(font->glyphs[t].name,charname)) {
+               msg("<debug> Char [%d,>%s<,%d] maps to %d\n", charnr, charname, u, t);
+               return t;
+           }
+       }
+       /* if we didn't find the character, maybe
+          we can find the capitalized version */
+       for(t=0;t<font->num_glyphs;t++) {
+           if(font->glyphs[t].name && !strcasecmp(font->glyphs[t].name,charname)) {
+               msg("<debug> Char [%d,>>%s<<,%d] maps to %d\n", charnr, charname, u, t);
+               return t;
+           }
+       }
+    }
+
+    /* try to use the unicode id */
+    if(u>=0 && u<font->max_unicode && font->unicode2glyph[u]>=0) {
+       msg("<debug> Char [%d,%s,>%d<] maps to %d\n", charnr, charname, u, font->unicode2glyph[u]);
+       return font->unicode2glyph[u];
+    }
+
+    if(charnr>=0 && charnr<font->num_glyphs) {
+       msg("<debug> Char [>%d<,%s,%d] maps to %d\n", charnr, charname, u, charnr);
+       return charnr;
+    }
+    
+    return -1;
+}
+
+
 void SWFOutputDev::beginString(GfxState *state, GString *s) 
 { 
     int render = state->getRender();
-    msg("<trace> beginString(%s) render=%d", s->getCString(), render);
+    if(current_text_stroke) {
+       msg("<error> Error: Incompatible change of text rendering to %d while inside cliptext", render);
+    }
+
+    msg("<trace> beginString(%s) render=%d", makeStringPrintable(s->getCString()), render);
     double m11,m21,m12,m22;
 //    msg("<debug> %s beginstring \"%s\"\n", gfxstate2str(state), s->getCString());
     state->getFontTransMat(&m11, &m12, &m21, &m22);
     m11 *= state->getHorizScaling();
     m21 *= state->getHorizScaling();
-    swfoutput_setfontmatrix(&output, m11, -m21, m12, -m22);
-    if(render != 3 && render != 0)
-       msg("<warning> Text rendering mode %d (%s) not fully supported yet (for text \"%s\")", render, renderModeDesc[render&7], makeStringPrintable(s->getCString()));
+
+    this->current_font_matrix.m00 = m11 / 1024.0;
+    this->current_font_matrix.m01 = m12 / 1024.0;
+    this->current_font_matrix.m10 = -m21 / 1024.0;
+    this->current_font_matrix.m11 = -m22 / 1024.0;
+    this->current_font_matrix.tx = 0;
+    this->current_font_matrix.ty = 0;
+
+    gfxmatrix_t m = this->current_font_matrix;
+
+    /*if(render != 3 && render != 0)
+       msg("<warning> Text rendering mode %d (%s) not fully supported yet (for text \"%s\")", render, renderModeDesc[render&7], makeStringPrintable(s->getCString()));*/
     states[statepos].textRender = render;
 }
 
-static int textCount = 0;
-
 void SWFOutputDev::drawChar(GfxState *state, double x, double y,
                        double dx, double dy,
                        double originX, double originY,
                        CharCode c, Unicode *_u, int uLen)
 {
-    textCount++;
-
     int render = state->getRender();
     // check for invisible text -- this is used by Acrobat Capture
     if (render == 3)
@@ -920,14 +1022,7 @@ void SWFOutputDev::drawChar(GfxState *state, double x, double y,
     if(states[statepos].textRender != render)
        msg("<error> Internal error: drawChar.render!=beginString.render");
 
-    GfxRGB rgb;
-    double opaq = state->getFillOpacity();
-    state->getFillRGB(&rgb);
-    gfxcolor_t col;
-    col.r = (unsigned char)(rgb.r*255);
-    col.g = (unsigned char)(rgb.g*255);
-    col.b = (unsigned char)(rgb.b*255);
-    col.a = (unsigned char)(opaq*255);
+    gfxcolor_t col = getFillColor(state);
 
     Gushort *CIDToGIDMap = 0;
     GfxFont*font = state->getFont();
@@ -937,10 +1032,6 @@ void SWFOutputDev::drawChar(GfxState *state, double x, double y,
        msg("<debug> type3 char at %f/%f", x, y);
        return;
     }
-    double x1,y1;
-    x1 = x;
-    y1 = y;
-    state->transform(x, y, &x1, &y1);
     
     Unicode u=0;
     char*name=0;
@@ -973,24 +1064,83 @@ void SWFOutputDev::drawChar(GfxState *state, double x, double y,
        if(enc && enc[c])
           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);
-       swfoutput_drawchar(&output, x1, y1, name, CIDToGIDMap[c], u, &col);
+       c = CIDToGIDMap[c];
     } else {
        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);
-       swfoutput_drawchar(&output, x1, y1, name, c, u, &col);
+    }
+
+    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);
+       return;
+    }
+
+    gfxmatrix_t m = this->current_font_matrix;
+    state->transform(x, y, &m.tx, &m.ty);
+
+    if(render == RENDER_FILL) {
+       swfoutput_gfxdrawchar(&output, current_font_id, charid, &col, &m);
+    } else {
+       msg("<debug> Drawing glyph %d as shape", charid);
+       gfxline_t*glyph = current_gfxfont->glyphs[charid].line;
+       gfxline_t*tglyph = gfxline_clone(glyph);
+       gfxline_transform(tglyph, &m);
+       if((render&3) != RENDER_INVISIBLE) {
+           gfxline_t*add = gfxline_clone(tglyph);
+           current_text_stroke = gfxline_append(current_text_stroke, add);
+       }
+       if(render&RENDER_CLIP) {
+           gfxline_t*add = gfxline_clone(tglyph);
+           current_text_clip = gfxline_append(current_text_clip, add);
+       }
+       gfxline_free(tglyph);
     }
 }
 
 void SWFOutputDev::endString(GfxState *state) 
 { 
-    msg("<trace> endString()");
+    int render = state->getRender();
+    msg("<trace> endString() render=%d textstroke=%08x", render, current_text_stroke);
+    if(states[statepos].textRender != render)
+       msg("<error> Internal error: drawChar.render!=beginString.render");
+    
+    if(current_text_stroke) {
+       /* fillstroke and stroke text rendering objects we can process right
+          now (as there may be texts of other rendering modes in this
+          text object)- clipping objects have to wait until endTextObject,
+          however */
+       if((render&3) == RENDER_FILL) {
+           fillGfxLine(state, current_text_stroke);
+           gfxline_free(current_text_stroke);
+           current_text_stroke = 0;
+       } else if((render&3) == RENDER_FILLSTROKE) {
+           fillGfxLine(state, current_text_stroke);
+           strokeGfxline(state, current_text_stroke);
+           gfxline_free(current_text_stroke);
+           current_text_stroke = 0;
+       } else if((render&3) == RENDER_STROKE) {
+           strokeGfxline(state, current_text_stroke);
+           gfxline_free(current_text_stroke);
+           current_text_stroke = 0;
+       }
+    }
 }    
 
 void SWFOutputDev::endTextObject(GfxState *state)
 {
-    msg("<trace> endTextObject()");
+    int render = state->getRender();
+    msg("<trace> endTextObject() render=%d textstroke=%08x clipstroke=%08x", render, current_text_stroke, current_text_clip);
+    if(states[statepos].textRender != render)
+       msg("<error> Internal error: drawChar.render!=beginString.render");
+    
+    if(current_text_clip) {
+       clipToGfxLine(state, current_text_clip);
+       gfxline_free(current_text_clip);
+       current_text_clip = 0;
+    }
 }
 
 /* the logic seems to be as following:
@@ -1347,7 +1497,8 @@ char*SWFOutputDev::writeEmbeddedFontToFile(XRef*ref, GfxFont*font)
        msg("<notice> Collection: %s", c.getCString());
     }*/
 
-    if (font->getType() == fontType1C) {
+    //if (font->getType() == fontType1C) {
+    if (0) { //font->getType() == fontType1C) {
       if (!(fontBuf = font->readEmbFontFile(xref, &fontLen))) {
        fclose(f);
        msg("<error> Couldn't read embedded font file");
@@ -1598,6 +1749,40 @@ void SWFOutputDev::setXRef(PDFDoc*doc, XRef *xref)
     this->xref = xref;
 }
 
+int SWFOutputDev::setGfxFont(char*id, char*filename)
+{
+    gfxfont_t*font = 0;
+    fontlist_t*last=0,*l = this->fontlist;
+
+    /* TODO: should this be part of the state? */
+    while(l) {
+       last = l;
+       if(!strcmp(l->id, id)) {
+           current_font_id = l->id;
+           current_gfxfont = l->font;
+           font = l->font;
+           swfoutput_gfxaddfont(&this->output, id, current_gfxfont);
+           return 1;
+       }
+       l = l->next;
+    }
+    if(!filename) return 0;
+    font = gfxfont_load(filename);
+    l = new fontlist_t;
+    l->font = font;
+    l->filename = strdup(filename);
+    l->id = strdup(id);
+    l->next = 0;
+    current_font_id = l->id;
+    current_gfxfont = l->font;
+    if(last) {
+       last->next = l;
+    } else {
+       this->fontlist = l;
+    }
+    swfoutput_gfxaddfont(&this->output, id, current_gfxfont);
+    return 1;
+}
 
 void SWFOutputDev::updateFont(GfxState *state) 
 {
@@ -1620,16 +1805,18 @@ void SWFOutputDev::updateFont(GfxState *state)
        }
     }
 
-    /* second, see if swfoutput already has this font
-       cached- if so, we are done */
-    if(swfoutput_queryfont(&output, fontid))
-    {
+    /* second, see if this is a font which was used before-
+       if so, we are done */
+    if(setGfxFont(fontid, 0)) {
+       free(fontid);
+       return;
+    }
+/*    if(swfoutput_queryfont(&output, fontid))
        swfoutput_setfont(&output, fontid, 0);
        
        msg("<debug> updateFont(%s) [cached]", fontid);
-       free(fontid);
        return;
-    }
+    }*/
 
     // look for Type 3 font
     if (gfxFont->getType() == fontType3) {
@@ -1682,7 +1869,11 @@ void SWFOutputDev::updateFont(GfxState *state)
     msg("<verbose> updateFont(%s) -> %s", fontid, fileName);
     dumpFontInfo("<verbose>", gfxFont);
 
-    swfoutput_setfont(&output, fontid, fileName);
+    //swfoutput_setfont(&output, fontid, fileName);
+    
+    if(!setGfxFont(fontid, 0)) {
+       setGfxFont(fontid, fileName);
+    }
    
     if(fileName && del)
        unlinkfont(fileName);
@@ -2019,7 +2210,7 @@ void pdfswf_setparameter(char*name, char*value)
        caplinewidth = atof(value);
     } else if(!strcmp(name, "zoom")) {
        char buf[80];
-       zoom = atoi(value);
+       zoom = atof(value);
        sprintf(buf, "%f", (double)jpeg_dpi/(double)zoom);
        swfoutput_setparameter("jpegsubpixels", buf);
        sprintf(buf, "%f", (double)ppm_dpi/(double)zoom);