X-Git-Url: http://git.asbjorn.biz/?a=blobdiff_plain;f=pdf2swf%2FSWFOutputDev.cc;h=98a6db256fd472d65fa15ca1a36eaf85c9b57365;hb=b9b2c8cf73300427c3becd50dca9d5579c6a5cfa;hp=b0fd6cb39341f2c686130a966c9daee195f96a72;hpb=242870f51bcada26c934f5229f1d7aac9117eee5;p=swftools.git diff --git a/pdf2swf/SWFOutputDev.cc b/pdf2swf/SWFOutputDev.cc index b0fd6cb..98a6db2 100644 --- a/pdf2swf/SWFOutputDev.cc +++ b/pdf2swf/SWFOutputDev.cc @@ -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 */ @@ -143,8 +138,9 @@ typedef struct _fontlist _fontlist*next; } fontlist_t; +class InfoOutputDev; + class SWFOutputDev: public OutputDev { - int outputstarted; public: gfxdevice_t* output; @@ -156,12 +152,18 @@ 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); - void pagefeed(); - void* getSWF(); + + // Start a page. + void startFrame(int width, int height); - void getDimensions(int*x1,int*y1,int*x2,int*y2); + virtual void startPage(int pageNum, GfxState *state, double x1, double y1, double x2, double y2) ; + + void endframe(); + void* getSWF(); //----- get info about output device @@ -181,9 +183,6 @@ public: void setXRef(PDFDoc*doc, XRef *xref); - // Start a page. - virtual void startPage(int pageNum, GfxState *state, double x1, double y1, double x2, double y2) ; - //----- link borders virtual void drawLink(Link *link, Catalog *catalog) ; @@ -246,7 +245,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); @@ -257,6 +256,7 @@ public: char outer_clip_box; //whether the page clip box is still on + InfoOutputDev*info; SWFOutputState states[64]; int statepos; @@ -269,6 +269,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? @@ -295,12 +296,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; @@ -312,9 +372,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;} @@ -336,14 +398,53 @@ class InfoOutputDev: public OutputDev { num_links++; } + virtual double getMaximumFontSize(char*id) + { + FontInfo*info = (FontInfo*)id2font->lookup(id); + if(!info) { + msg(" 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, @@ -362,12 +463,12 @@ class InfoOutputDev: public OutputDev SWFOutputDev::SWFOutputDev() { jpeginfo = 0; + textmodeinfo = 0; ttfinfo = 0; linkinfo = 0; pbminfo = 0; type3active = 0; statepos = 0; - outputstarted = 0; xref = 0; substitutepos = 0; type3Warning = 0; @@ -382,8 +483,17 @@ SWFOutputDev::SWFOutputDev() fontlist = 0; result = 0; outer_clip_box = 0; + pages = 0; + pagebuflen = 0; + pagepos = 0; output = (gfxdevice_t*)malloc(sizeof(gfxdevice_t)); - memset(output, 0, sizeof(output)); + gfxdevice_swf_init(output); + /* configure device */ + parameter_t*p = device_config; + while(p) { + output->setparameter(output, p->name, p->value); + p = p->next; + } }; void SWFOutputDev::setMove(int x,int y) @@ -403,18 +513,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(); @@ -708,7 +806,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; @@ -789,6 +887,7 @@ void SWFOutputDev::strokeGfxline(GfxState *state, gfxline_t*line) line2 = gfxtool_dash_line(line, dash, dashphase); line = line2; + free(dash); msg(" After dashing:"); } @@ -892,31 +991,22 @@ void SWFOutputDev::eoClip(GfxState *state) gfxline_free(line); } -/* pass through functions for swf_output */ -int SWFOutputDev::save(char*filename) -{ - finish(); - return result->save(result, filename); -} -void SWFOutputDev::pagefeed() +void SWFOutputDev::endframe() { if(outer_clip_box) { output->endclip(output); outer_clip_box = 0; } - swfoutput_pagefeed(output); -} -void* SWFOutputDev::getSWF() -{ - finish(); - return result->get(result, "swf"); + output->endpage(output); } void SWFOutputDev::finish() { if(outer_clip_box) { - output->endclip(output); + if(output) { + output->endclip(output); + } outer_clip_box = 0; } if(output) { @@ -925,15 +1015,29 @@ void SWFOutputDev::finish() } } +int SWFOutputDev::save(char*filename) +{ + finish(); + return result->save(result, filename); +} +void* SWFOutputDev::getSWF() +{ + finish(); + return result->get(result, "swf"); +} + SWFOutputDev::~SWFOutputDev() { finish(); - outputstarted = 0; if(this->result) { this->result->destroy(this->result); this->result = 0; } + + if(this->pages) { + free(this->pages); this->pages = 0; + } fontlist_t*l = this->fontlist; while(l) { @@ -945,10 +1049,10 @@ SWFOutputDev::~SWFOutputDev() free(l); l = next; } + this->fontlist = 0; }; GBool SWFOutputDev::upsideDown() { - msg(" upsidedown? yes"); return gTrue; }; GBool SWFOutputDev::useDrawChar() @@ -1027,6 +1131,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 && charnrnum_glyphs) { msg(" Char [>%d<,%s,%d] maps to %d\n", charnr, charname, u, charnr); return charnr; @@ -1069,10 +1178,13 @@ void SWFOutputDev::drawChar(GfxState *state, double x, double y, double originX, double originY, CharCode c, Unicode *_u, int uLen) { + msg(" drawChar(%f,%f,%d)", x,y,c); int render = state->getRender(); // check for invisible text -- this is used by Acrobat Capture - if (render == 3) + if (render == 3) { + msg(" Ignoring invisible text: char %d at %f,%f", c, x, y); return; + } if(states[statepos].textRender != render) msg(" Internal error: drawChar.render!=beginString.render"); @@ -1126,10 +1238,30 @@ void SWFOutputDev::drawChar(GfxState *state, double x, double y, msg(" 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(" 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(" ligature %d missing in font %s\n", c, current_font_id); + for(t=0;t 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; } @@ -1142,6 +1274,10 @@ void SWFOutputDev::drawChar(GfxState *state, double x, double y, output->drawchar(output, current_font_id, charid, &col, &m); } else { msg(" Drawing glyph %d as shape", charid); + if(!textmodeinfo) { + msg(" 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); @@ -1233,6 +1369,11 @@ void SWFOutputDev::endType3Char(GfxState *state) msg(" endType3Char"); } +void SWFOutputDev::startFrame(int width, int height) +{ + output->startpage(output, width, height); +} + void SWFOutputDev::startPage(int pageNum, GfxState *state, double crop_x1, double crop_y1, double crop_x2, double crop_y2) { this->currentpage = pageNum; @@ -1244,10 +1385,6 @@ void SWFOutputDev::startPage(int pageNum, GfxState *state, double crop_x1, doubl white.r = white.g = white.b = white.a = 255; - msg(" startPage %d (%f,%f,%f,%f)\n", pageNum, crop_x1, crop_y1, crop_x2, crop_y2); - if(rot!=0) - msg(" page is rotated %d degrees\n", rot); - /* state->transform(state->getX1(),state->getY1(),&x1,&y1); state->transform(state->getX2(),state->getY2(),&x2,&y2); Use CropBox, not MediaBox, as page size @@ -1257,12 +1394,13 @@ void SWFOutputDev::startPage(int pageNum, GfxState *state, double crop_x1, doubl y1 = crop_y1; x2 = crop_x2; y2 = crop_y2;*/ - state->transform(crop_x1,crop_y1,&x1,&y1); - state->transform(crop_x2,crop_y2,&x2,&y2); + state->transform(crop_x1,crop_y1,&x1,&y1); //x1 += user_movex; y1 += user_movey; + state->transform(crop_x2,crop_y2,&x2,&y2); //x2 += user_movex; y2 += user_movey; if(x2 x1)*/ x1 = user_clipx1; @@ -1271,29 +1409,16 @@ void SWFOutputDev::startPage(int pageNum, GfxState *state, double crop_x1, doubl /*if(user_clipy2 < y2)*/ y2 = user_clipy2; } - if(!outputstarted) { - msg(" Bounding box is (%f,%f)-(%f,%f)", x1,y1,x2,y2); - - gfxdevice_swf_init(output); - - /* configure device */ - parameter_t*p = device_config; - while(p) { - output->setparameter(output, p->name, p->value); - p = p->next; - } - - outputstarted = 1; - } - + //msg(" Bounding box is (%f,%f)-(%f,%f) [shifted by %d/%d]", x1,y1,x2,y2, user_movex, user_movey); + if(outer_clip_box) { output->endclip(output); outer_clip_box = 0; } - msg(" processing page %d (%dx%d:%d:%d)", pageNum, (int)x2-(int)x1,(int)y2-(int)y1, (int)x1, (int)y1); - - swfoutput_newpage(output, (int)x1, (int)y1, (int)x2, (int)y2); + msg(" processing PDF page %d (%dx%d:%d:%d) (move:%d:%d)", pageNum, (int)x2-(int)x1,(int)y2-(int)y1, (int)x1, (int)y1, user_movex, user_movey); + if(rot!=0) + msg(" page is rotated %d degrees\n", rot); clippath[0].type = gfx_moveTo;clippath[0].x = x1; clippath[0].y = y1; clippath[0].next = &clippath[1]; clippath[1].type = gfx_lineTo;clippath[1].x = x2; clippath[1].y = y1; clippath[1].next = &clippath[2]; @@ -1400,7 +1525,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")) { @@ -1451,13 +1577,19 @@ void SWFOutputDev::drawLink(Link *link, Catalog *catalog) if(page>0) { int t; - for(t=0;t=0) { char buf[80]; sprintf(buf, "page%d", t); output->drawlink(output, points, buf); + } else { + msg(" Invalid link to page %d", page); } } else if(url) @@ -1849,7 +1981,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; @@ -1867,7 +1999,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); @@ -1892,6 +2031,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- @@ -1907,7 +2051,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; } @@ -1950,6 +2094,7 @@ void SWFOutputDev::updateFont(GfxState *state) char * fontname = getFontName(gfxFont); fileName = searchFont(fontname); if(!fileName) showFontError(gfxFont,0); + free(fontname); } if(!fileName) { char * fontname = getFontName(gfxFont); @@ -1966,13 +2111,13 @@ void SWFOutputDev::updateFont(GfxState *state) return; } - msg(" updateFont(%s) -> %s", fontid, fileName); + msg(" updateFont(%s) -> %s (max size: %f)", fontid, fileName, maxSize); dumpFontInfo("", 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) @@ -2511,6 +2656,7 @@ typedef struct _pdf_doc_internal { int protect; PDFDoc*doc; + InfoOutputDev*info; } pdf_doc_internal_t; typedef struct _pdf_page_internal { @@ -2587,24 +2733,19 @@ pdf_doc_t* pdf_init(char*filename, char*userPassword) if(!i->doc->okToChange() || !i->doc->okToAddNotes()) i->protect = 1; } - - return pdf_doc; -} -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 @@ -2621,10 +2762,11 @@ void pdf_destroy(pdf_doc_t*pdf_doc) { pdf_doc_internal_t*i= (pdf_doc_internal_t*)pdf_doc->internal; - msg(" pdfswf.cc: pdfswf_close()"); 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; @@ -2666,24 +2808,53 @@ swf_output_t* swf_output_init() return swf_output; } -void swf_output_setparameter(swf_output_t*swf_output, char*name, char*value) +void swf_output_setparameter(swf_output_t*swf, char*name, char*value) { - /* FIXME */ pdfswf_setparameter(name, value); } -void swf_output_pagefeed(swf_output_t*swf) +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->pagefeed(); - i->outputDev->getDimensions(&swf->x1, &swf->y1, &swf->x2, &swf->y2); + i->outputDev->startFrame(width, height); +} + +void swf_output_endframe(swf_output_t*swf) +{ + swf_output_internal_t*i= (swf_output_internal_t*)swf->internal; + i->outputDev->endframe(); +} + +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; } @@ -2691,7 +2862,6 @@ void* swf_output_get(swf_output_t*swf) { 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); return ret; } @@ -2708,17 +2878,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(" 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) @@ -2775,4 +2950,5 @@ pdf_page_info_t* pdf_page_getinfo(pdf_page_t*page) void pdf_page_info_destroy(pdf_page_info_t*info) { free(info); + }