From 8fae5ea0ef11d8a447d0a7e93e045cc2d67b2cc7 Mon Sep 17 00:00:00 2001 From: David Mathog <> Date: Thu, 25 Oct 2012 10:08:38 +0200 Subject: changes_2012_10_22b.patch, changes_2012_10_24a.patch EMF import (Adobe Illustrator EMF files): - workaround for issue with page scaling ('MM_ANISOTROPIC', wrong units) - fix SETWORLDTRANSFORM operation - fix libUEMF to support older/shorter EMF header forms EMF import (general): - fix import of shapes (rectangles) without borders - handle EMF bitmap modes where a subsection of the image is extracted EMF export/import: - increased size in mm of the reference device by 100X on EMF export (significant when the dpi is calculated on reading the EMF back in) - changed dpi calculation: (sum of pixels ref device)/(sum of millimeter ref device) (bzr r11668.1.34) --- src/extension/internal/emf-inout.cpp | 356 ++++++++++++++++++++++++----------- src/extension/internal/emf-print.cpp | 42 ++--- src/extension/internal/uemf.c | 62 +++--- src/extension/internal/uemf.h | 7 +- src/extension/internal/uemf_endian.c | 48 ++++- src/extension/internal/uemf_print.c | 27 ++- 6 files changed, 372 insertions(+), 170 deletions(-) (limited to 'src/extension') diff --git a/src/extension/internal/emf-inout.cpp b/src/extension/internal/emf-inout.cpp index 8be4e998e..acc3443d5 100644 --- a/src/extension/internal/emf-inout.cpp +++ b/src/extension/internal/emf-inout.cpp @@ -390,17 +390,16 @@ typedef struct emf_callback_data { EMF_DEVICE_CONTEXT dc[EMF_MAX_DC+1]; // FIXME: This should be dynamic.. int level; - double xDPI, yDPI; - uint32_t mask; // Draw properties - int arcdir; //U_AD_COUNTERCLOCKWISE 1 or U_AD_CLOCKWISE 2 + double ulCornerX,ulCornerY; // Upper left corner, from header rclBounds, in logical units + double ydir; // 1.0 if y is positive DOWN (usual case), -1.0 if y is negative DOWN + uint32_t mask; // Draw properties + int arcdir; //U_AD_COUNTERCLOCKWISE 1 or U_AD_CLOCKWISE 2 - uint32_t dwRop2; // Binary raster operation, 0 if none (use brush/pen unmolested) - uint32_t dwRop3; // Ternary raster operation, 0 if none (use brush/pen unmolested) + uint32_t dwRop2; // Binary raster operation, 0 if none (use brush/pen unmolested) + uint32_t dwRop3; // Ternary raster operation, 0 if none (use brush/pen unmolested) float MMX; float MMY; - float dwInchesX; - float dwInchesY; unsigned int id; unsigned int drawtype; // one of 0 or U_EMR_FILLPATH, U_EMR_STROKEPATH, U_EMR_STROKEANDFILLPATH @@ -629,17 +628,20 @@ uint32_t add_image(PEMF_CALLBACK_DATA d, void *pEmr, uint32_t cbBits, uint32_t ct, // DIB color table numCt, // DIB color table number of entries &rgba_px, // U_RGBA pixel array (32 bits), created by this routine, caller must free. - width, // Width of pixel array - height, // Height of pixel array + width, // Width of pixel array in record + height, // Height of pixel array in record colortype, // DIB BitCount Enumeration numCt, // Color table used if not 0 - invert // If DIB rows are in opposite order from RGBA rows + invert, // If DIB rows are in opposite order from RGBA rows + 0,0, // start position in pixel array in record + width, // Width of extracted pixel array + height // Height of extracted pixel array ) && rgba_px) { toPNG( // Get the image from the RGBA px into mempng &mempng, - width, height, + width, height, // of the SRC bitmap rgba_px); free(rgba_px); } @@ -888,17 +890,23 @@ output_style(PEMF_CALLBACK_DATA d, int iType) static double _pix_x_to_point(PEMF_CALLBACK_DATA d, double px) { - double tmp = px - d->dc[d->level].winorg.x; - tmp *= d->dc[d->level].ScaleInX ? d->dc[d->level].ScaleInX : 1.0; + double scale = (d->dc[d->level].ScaleInX ? d->dc[d->level].ScaleInX : 1.0); + double tmp = px; + tmp -= d->dc[d->level].winorg.x; + tmp *= scale; + tmp -= d->ulCornerX; tmp += d->dc[d->level].vieworg.x; return tmp; } static double -_pix_y_to_point(PEMF_CALLBACK_DATA d, double px) +_pix_y_to_point(PEMF_CALLBACK_DATA d, double py) { - double tmp = px - d->dc[d->level].winorg.y; - tmp *= d->dc[d->level].ScaleInY ? d->dc[d->level].ScaleInY : 1.0; + double scale = (d->dc[d->level].ScaleInY ? d->dc[d->level].ScaleInY : 1.0); + double tmp = py; + tmp -= d->dc[d->level].winorg.y; + tmp *= scale; + tmp += d->ydir*d->ulCornerY; tmp += d->dc[d->level].vieworg.y; return tmp; } @@ -907,10 +915,13 @@ _pix_y_to_point(PEMF_CALLBACK_DATA d, double px) static double pix_to_x_point(PEMF_CALLBACK_DATA d, double px, double py) { + double wpx = px * d->dc[d->level].worldTransform.eM11 + py * d->dc[d->level].worldTransform.eM21 + d->dc[d->level].worldTransform.eDx; + double x = _pix_x_to_point(d, wpx); +/* double ppx = _pix_x_to_point(d, px); double ppy = _pix_y_to_point(d, py); - double x = ppx * d->dc[d->level].worldTransform.eM11 + ppy * d->dc[d->level].worldTransform.eM21 + d->dc[d->level].worldTransform.eDx; +*/ x *= device_scale; return x; @@ -919,13 +930,18 @@ pix_to_x_point(PEMF_CALLBACK_DATA d, double px, double py) static double pix_to_y_point(PEMF_CALLBACK_DATA d, double px, double py) { + + double wpy = px * d->dc[d->level].worldTransform.eM12 + py * d->dc[d->level].worldTransform.eM22 + d->dc[d->level].worldTransform.eDy; + double y = _pix_y_to_point(d, wpy); +/* double ppx = _pix_x_to_point(d, px); double ppy = _pix_y_to_point(d, py); - double y = ppx * d->dc[d->level].worldTransform.eM12 + ppy * d->dc[d->level].worldTransform.eM22 + d->dc[d->level].worldTransform.eDy; +*/ y *= device_scale; return y; + } static double @@ -1123,8 +1139,13 @@ select_extpen(PEMF_CALLBACK_DATA d, int index) d->dc[d->level].style.stroke_dasharray_set = 1; break; } - case U_PS_SOLID: +/* includes these for now, some should maybe not be in here + case U_PS_NULL: + case U_PS_INSIDEFRAME: + case U_PS_ALTERNATE: + case U_PS_STYLE_MASK: +*/ default: { d->dc[d->level].style.stroke_dasharray_set = 0; @@ -1172,51 +1193,60 @@ select_extpen(PEMF_CALLBACK_DATA d, int index) d->dc[d->level].stroke_set = true; - if (pEmr->elp.elpPenStyle == U_PS_NULL) { + if (pEmr->elp.elpPenStyle == U_PS_NULL) { // draw nothing, but fill out all the values with something + double r, g, b; + r = SP_COLOR_U_TO_F( U_RGBAGetR(d->dc[d->level].textColor)); + g = SP_COLOR_U_TO_F( U_RGBAGetG(d->dc[d->level].textColor)); + b = SP_COLOR_U_TO_F( U_RGBAGetB(d->dc[d->level].textColor)); + d->dc[d->level].style.stroke.value.color.set( r, g, b ); d->dc[d->level].style.stroke_width.value = 0; d->dc[d->level].stroke_set = false; - } else if (pEmr->elp.elpWidth) { - int cur_level = d->level; - d->level = d->emf_obj[index].level; - double pen_width = pix_to_size_point( d, pEmr->elp.elpWidth ); - d->level = cur_level; - d->dc[d->level].style.stroke_width.value = pen_width; - } else { // this stroke should always be rendered as 1 pixel wide, independent of zoom level (can that be done in SVG?) - //d->dc[d->level].style.stroke_width.value = 1.0; - int cur_level = d->level; - d->level = d->emf_obj[index].level; - double pen_width = pix_to_size_point( d, 1 ); - d->level = cur_level; - d->dc[d->level].style.stroke_width.value = pen_width; + d->dc[d->level].stroke_mode = DRAW_PAINT; } + else { + if (pEmr->elp.elpWidth) { + int cur_level = d->level; + d->level = d->emf_obj[index].level; + double pen_width = pix_to_size_point( d, pEmr->elp.elpWidth ); + d->level = cur_level; + d->dc[d->level].style.stroke_width.value = pen_width; + } else { // this stroke should always be rendered as 1 pixel wide, independent of zoom level (can that be done in SVG?) + //d->dc[d->level].style.stroke_width.value = 1.0; + int cur_level = d->level; + d->level = d->emf_obj[index].level; + double pen_width = pix_to_size_point( d, 1 ); + d->level = cur_level; + d->dc[d->level].style.stroke_width.value = pen_width; + } - if( pEmr->elp.elpBrushStyle == U_BS_SOLID){ - double r, g, b; - r = SP_COLOR_U_TO_F( U_RGBAGetR(pEmr->elp.elpColor) ); - g = SP_COLOR_U_TO_F( U_RGBAGetG(pEmr->elp.elpColor) ); - b = SP_COLOR_U_TO_F( U_RGBAGetB(pEmr->elp.elpColor) ); - d->dc[d->level].style.stroke.value.color.set( r, g, b ); - d->dc[d->level].stroke_mode = DRAW_PAINT; - d->dc[d->level].stroke_set = true; - } - else if(pEmr->elp.elpBrushStyle == U_BS_HATCHED){ - d->dc[d->level].stroke_idx = add_hatch(d, pEmr->elp.elpHatch, pEmr->elp.elpColor); - d->dc[d->level].stroke_mode = DRAW_PATTERN; - d->dc[d->level].stroke_set = true; - } - else if(pEmr->elp.elpBrushStyle == U_BS_DIBPATTERN || pEmr->elp.elpBrushStyle == U_BS_DIBPATTERNPT){ - d->dc[d->level].stroke_idx = add_image(d, pEmr, pEmr->cbBits, pEmr->cbBmi, *(uint32_t *) &(pEmr->elp.elpColor), pEmr->offBits, pEmr->offBmi); - d->dc[d->level].stroke_mode = DRAW_IMAGE; - d->dc[d->level].stroke_set = true; - } - else { // U_BS_PATTERN and anything strange that falls in, stroke is solid textColor - double r, g, b; - r = SP_COLOR_U_TO_F( U_RGBAGetR(d->dc[d->level].textColor)); - g = SP_COLOR_U_TO_F( U_RGBAGetG(d->dc[d->level].textColor)); - b = SP_COLOR_U_TO_F( U_RGBAGetB(d->dc[d->level].textColor)); - d->dc[d->level].style.stroke.value.color.set( r, g, b ); - d->dc[d->level].stroke_mode = DRAW_PAINT; - d->dc[d->level].stroke_set = true; + if( pEmr->elp.elpBrushStyle == U_BS_SOLID){ + double r, g, b; + r = SP_COLOR_U_TO_F( U_RGBAGetR(pEmr->elp.elpColor) ); + g = SP_COLOR_U_TO_F( U_RGBAGetG(pEmr->elp.elpColor) ); + b = SP_COLOR_U_TO_F( U_RGBAGetB(pEmr->elp.elpColor) ); + d->dc[d->level].style.stroke.value.color.set( r, g, b ); + d->dc[d->level].stroke_mode = DRAW_PAINT; + d->dc[d->level].stroke_set = true; + } + else if(pEmr->elp.elpBrushStyle == U_BS_HATCHED){ + d->dc[d->level].stroke_idx = add_hatch(d, pEmr->elp.elpHatch, pEmr->elp.elpColor); + d->dc[d->level].stroke_mode = DRAW_PATTERN; + d->dc[d->level].stroke_set = true; + } + else if(pEmr->elp.elpBrushStyle == U_BS_DIBPATTERN || pEmr->elp.elpBrushStyle == U_BS_DIBPATTERNPT){ + d->dc[d->level].stroke_idx = add_image(d, pEmr, pEmr->cbBits, pEmr->cbBmi, *(uint32_t *) &(pEmr->elp.elpColor), pEmr->offBits, pEmr->offBmi); + d->dc[d->level].stroke_mode = DRAW_IMAGE; + d->dc[d->level].stroke_set = true; + } + else { // U_BS_PATTERN and anything strange that falls in, stroke is solid textColor + double r, g, b; + r = SP_COLOR_U_TO_F( U_RGBAGetR(d->dc[d->level].textColor)); + g = SP_COLOR_U_TO_F( U_RGBAGetG(d->dc[d->level].textColor)); + b = SP_COLOR_U_TO_F( U_RGBAGetB(d->dc[d->level].textColor)); + d->dc[d->level].style.stroke.value.color.set( r, g, b ); + d->dc[d->level].stroke_mode = DRAW_PAINT; + d->dc[d->level].stroke_set = true; + } } } @@ -1352,6 +1382,24 @@ insert_object(PEMF_CALLBACK_DATA d, int index, int type, PU_ENHMETARECORD pObj) } } +/* Identify probable Adobe Illustrator produced EMF files, which do strange things with the scaling. + The few so far observed all had this format. +*/ +int AI_hack(PU_EMRHEADER pEmr){ + int ret=0; + char *ptr; + ptr = (char *)pEmr; + PU_EMRSETMAPMODE nEmr = (PU_EMRSETMAPMODE) (ptr + pEmr->emr.nSize); + char *string = NULL; + if(pEmr->nDescription)string = U_Utf16leToUtf8((uint16_t *)((char *) pEmr + pEmr->offDescription), pEmr->nDescription, NULL); + if((pEmr->nDescription >= 13) && + (0==strcmp("Adobe Systems",string)) && + (nEmr->emr.iType == U_EMR_SETMAPMODE) && + (nEmr->iMode == U_MM_ANISOTROPIC)){ ret=1; } + if(string)free(string); + return(ret); +} + /** \fn create a UTF-32LE buffer and fill it with UNICODE unknown character \param count number of copies of the Unicode unknown character to fill with @@ -1364,10 +1412,27 @@ uint32_t *unknown_chars(size_t count){ return res; } -void common_image_extraction(PEMF_CALLBACK_DATA d, void *pEmr, double l, double t, double r, double b, +/** + \fn store SVG for an image given the pixmap and various coordinate information + \param d + \param pEmr + \param dl (double) destination left in inkscape pixels + \param dt (double) destination top in inkscape pixels + \param dr (double) destination right in inkscape pixels + \param db (double) destination bottom in inkscape pixels + \param sl (int) source left in pixels in the src image + \param st (int) source top in pixels in the src image + \param iUsage + \param offBits + \param cbBits + \param offBmi + \param cbBmi +*/ +void common_image_extraction(PEMF_CALLBACK_DATA d, void *pEmr, + double dl, double dt, double dr, double db, int sl, int st, int sw, int sh, uint32_t iUsage, uint32_t offBits, uint32_t cbBits, uint32_t offBmi, uint32_t cbBmi){ SVGOStringStream tmp_image; - tmp_image << " y=\"" << t << "\"\n x=\"" << l <<"\"\n "; + tmp_image << " y=\"" << dt << "\"\n x=\"" << dl <<"\"\n "; // The image ID is filled in much later when tmp_image is converted @@ -1395,6 +1460,10 @@ void common_image_extraction(PEMF_CALLBACK_DATA d, void *pEmr, double l, double &colortype, &invert )){ + if(sw == 0 || sl == 0){ + sw = width; + sh = height; + } if(!DIB_to_RGBA( px, // DIB pixel array @@ -1405,13 +1474,15 @@ void common_image_extraction(PEMF_CALLBACK_DATA d, void *pEmr, double l, double height, // Height of pixel array colortype, // DIB BitCount Enumeration numCt, // Color table used if not 0 - invert // If DIB rows are in opposite order from RGBA rows + invert, // If DIB rows are in opposite order from RGBA rows + sl,st, // starting point in pixel array + sw,sh // columns/rows to extract from the pixel array (output array size) ) && rgba_px) { toPNG( // Get the image from the RGBA px into mempng &mempng, - width, height, + sw, sh, // size of the extracted pixel array rgba_px); free(rgba_px); } @@ -1427,7 +1498,7 @@ void common_image_extraction(PEMF_CALLBACK_DATA d, void *pEmr, double l, double tmp_image << "iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAIAAAA7ljmRAAAAA3NCSVQICAjb4U/gAAAALElEQVQImQXBQQ2AMAAAsUJQMSWI2H8qME1yMshojwrvGB8XcHKvR1XtOTc/8HENumHCsOMAAAAASUVORK5CYII="; } - tmp_image << "\"\n height=\"" << b-t+1 << "\"\n width=\"" << r-l+1 << "\"\n"; + tmp_image << "\"\n height=\"" << db-dt+1 << "\"\n width=\"" << dr-dl+1 << "\"\n"; *(d->outsvg) += "\n\t outsvg) += tmp_image.str().c_str(); @@ -1535,29 +1606,42 @@ std::cout << "BEFORE DRAW" tmp_outdef << " xmlns:sodipodi=\"http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd\"\n"; // needed for sodipodi:role tmp_outdef << " version=\"1.0\"\n"; - d->xDPI = 2540; - d->yDPI = 2540; + d->ulCornerX = pEmr->rclBounds.left; // Upper left corner, from header rclBounds, in logical units, usually both 0, but not always + d->ulCornerY = pEmr->rclBounds.top;; - d->dc[d->level].PixelsInX = pEmr->rclFrame.right; // - pEmr->rclFrame.left; - d->dc[d->level].PixelsInY = pEmr->rclFrame.bottom; // - pEmr->rclFrame.top; - - d->MMX = d->dc[d->level].PixelsInX / 100.0; - d->MMY = d->dc[d->level].PixelsInY / 100.0; - - d->dc[d->level].PixelsOutX = d->MMX * PX_PER_MM; - d->dc[d->level].PixelsOutY = d->MMY * PX_PER_MM; + if(pEmr->rclFrame.bottom < 0 || pEmr->rclFrame.top < 0){ d->ydir = -1.0; } + else { d->ydir = 1.0; } + /* inclusive-inclusive, so the size is 1 more than the difference */ + d->dc[d->level].PixelsInX = pEmr->rclFrame.right - pEmr->rclFrame.left + 1; + d->dc[d->level].PixelsInY = pEmr->rclFrame.bottom - pEmr->rclFrame.top + 1; /* calculate ratio of Inkscape dpi/device dpi - This can cause problems later due to accuracy limits in the EMF. A super high resolution + This can cause problems later due to accuracy limits in the EMF. A high resolution EMF might have a final device_scale of 0.074998, and adjusting the (integer) device size by 1 will still not get it exactly to 0.075. Later when the font size is calculated it can end up as 29.9992 or 22.4994 instead of the intended 30 or 22.5. This is handled by - snapping font sizes to the nearest .01. + snapping font sizes to the nearest .01. The best estimate is made by using both values. */ - if (pEmr->szlMillimeters.cx && pEmr->szlDevice.cx) - device_scale = PX_PER_MM*pEmr->szlMillimeters.cx/pEmr->szlDevice.cx; - + if (pEmr->szlMillimeters.cx && pEmr->szlDevice.cx) device_scale = PX_PER_MM * + (pEmr->szlMillimeters.cx + pEmr->szlMillimeters.cy)/( pEmr->szlDevice.cx + pEmr->szlDevice.cy); + + /* Adobe Illustrator files set mapmode to MM_ANISOTROPIC and somehow or other this + converts the rclFrame values from MM_HIMETRIC to MM_HIENGLISH, with another factor of 3 thrown + in for good measure. Ours not to question why... + */ + if(AI_hack(pEmr)){ + d->dc[d->level].PixelsInX *= 25.4/(10.0*3.0); + d->dc[d->level].PixelsInY *= 25.4/(10.0*3.0); + device_scale *= 25.4/(10.0*3.0); + } + + d->MMX = d->dc[d->level].PixelsInX / 100.0; + d->MMY = d->dc[d->level].PixelsInY / 100.0; + + d->dc[d->level].PixelsOutX = d->MMX * PX_PER_MM; + d->dc[d->level].PixelsOutY = d->MMY * PX_PER_MM; + tmp_outdef << " width=\"" << d->MMX << "mm\"\n" << " height=\"" << d->MMY << "mm\">\n"; @@ -1849,7 +1933,43 @@ std::cout << "BEFORE DRAW" } case U_EMR_SETPIXELV: dbg_str << "\n"; break; case U_EMR_SETMAPPERFLAGS: dbg_str << "\n"; break; - case U_EMR_SETMAPMODE: dbg_str << "\n"; break; + case U_EMR_SETMAPMODE: + { + dbg_str << "\n"; + PU_EMRSETMAPMODE pEmr = (PU_EMRSETMAPMODE) lpEMFR; + switch (pEmr->iMode){ + case U_MM_TEXT: + default: + d->ydir = 1.0; + // leave device_scale as is, device_scale maps LU pixels to inkscape pixels as set in the EMF header + break; + case U_MM_LOMETRIC: // 1 LU = 0.1 mm + d->ydir = -1.0; + device_scale = 0.1 * PX_PER_MM; + break; + case U_MM_HIMETRIC: // 1 LU = 0.01 mm + d->ydir = -1.0; + device_scale = 0.01 * PX_PER_MM; + break; + case U_MM_LOENGLISH: // 1 LU = 0.1 in + d->ydir = -1.0; + device_scale = 0.1 * PX_PER_IN; + break; + case U_MM_HIENGLISH: // 1 LU = 0.01 in + d->ydir = -1.0; + device_scale = 0.01 * PX_PER_IN; + break; + case U_MM_TWIPS: // 1 LU = 1/1440 in + d->ydir = -1.0; + device_scale = (1.0/1440.0) * PX_PER_IN; + break; + case U_MM_ISOTROPIC: // let scaleX etc. handle it, as set by SETVIEWPORTEXTEX and SETWINDOWEXTEX + break; + case U_MM_ANISOTROPIC: + break; + } + break; + } case U_EMR_SETBKMODE: dbg_str << "\n"; break; case U_EMR_SETPOLYFILLMODE: { @@ -2540,20 +2660,27 @@ std::cout << "BEFORE DRAW" dbg_str << "\n"; PU_EMRBITBLT pEmr = (PU_EMRBITBLT) lpEMFR; - double l = pix_to_x_point( d, pEmr->Dest.x, pEmr->Dest.y); - double t = pix_to_y_point( d, pEmr->Dest.x, pEmr->Dest.y); - double r = pix_to_x_point( d, pEmr->Dest.x + pEmr->cDest.x, pEmr->Dest.y + pEmr->cDest.y); - double b = pix_to_y_point( d, pEmr->Dest.x + pEmr->cDest.x, pEmr->Dest.y + pEmr->cDest.y); + double dl = pix_to_x_point( d, pEmr->Dest.x, pEmr->Dest.y); + double dt = pix_to_y_point( d, pEmr->Dest.x, pEmr->Dest.y); + double dr = pix_to_x_point( d, pEmr->Dest.x + pEmr->cDest.x, pEmr->Dest.y + pEmr->cDest.y); + double db = pix_to_y_point( d, pEmr->Dest.x + pEmr->cDest.x, pEmr->Dest.y + pEmr->cDest.y); + //source position within the bitmap, in pixels + int sl = pEmr->Src.x + pEmr->xformSrc.eDx; + int st = pEmr->Src.y + pEmr->xformSrc.eDy; + int sw = 0; // extract all of the image + int sh = 0; + if(sl<0)sl=0; + if(st<0)st=0; // Treat all nonImage bitblts as a rectangular write. Definitely not correct, but at // least it leaves objects where the operations should have been. if (!pEmr->cbBmiSrc) { // should be an application of a DIBPATTERNBRUSHPT, use a solid color instead SVGOStringStream tmp_rectangle; - tmp_rectangle << "\n\tM " << l << " " << t << " "; - tmp_rectangle << "\n\tL " << r << " " << t << " "; - tmp_rectangle << "\n\tL " << r << " " << b << " "; - tmp_rectangle << "\n\tL " << l << " " << b << " "; + tmp_rectangle << "\n\tM " << dl << " " << dt << " "; + tmp_rectangle << "\n\tL " << dr << " " << dt << " "; + tmp_rectangle << "\n\tL " << dr << " " << db << " "; + tmp_rectangle << "\n\tL " << dl << " " << db << " "; tmp_rectangle << "\n\tz"; d->mask |= emr_mask; @@ -2563,7 +2690,7 @@ std::cout << "BEFORE DRAW" tmp_path << tmp_rectangle.str().c_str(); } else { - common_image_extraction(d,pEmr,l,t,r,b, + common_image_extraction(d,pEmr,dl,dt,dr,db,sl,st,sw,sh, pEmr->iUsageSrc, pEmr->offBitsSrc, pEmr->cbBitsSrc, pEmr->offBmiSrc, pEmr->cbBmiSrc); } break; @@ -2574,11 +2701,18 @@ std::cout << "BEFORE DRAW" PU_EMRSTRETCHBLT pEmr = (PU_EMRSTRETCHBLT) lpEMFR; // Always grab image, ignore modes. if (pEmr->cbBmiSrc) { - double l = pix_to_x_point( d, pEmr->Dest.x, pEmr->Dest.y); - double t = pix_to_y_point( d, pEmr->Dest.x, pEmr->Dest.y); - double r = pix_to_x_point( d, pEmr->Dest.x + pEmr->cDest.x, pEmr->Dest.y + pEmr->cDest.y); - double b = pix_to_y_point( d, pEmr->Dest.x + pEmr->cDest.x, pEmr->Dest.y + pEmr->cDest.y); - common_image_extraction(d,pEmr,l,t,r,b, + double dl = pix_to_x_point( d, pEmr->Dest.x, pEmr->Dest.y); + double dt = pix_to_y_point( d, pEmr->Dest.x, pEmr->Dest.y); + double dr = pix_to_x_point( d, pEmr->Dest.x + pEmr->cDest.x, pEmr->Dest.y + pEmr->cDest.y); + double db = pix_to_y_point( d, pEmr->Dest.x + pEmr->cDest.x, pEmr->Dest.y + pEmr->cDest.y); + //source position within the bitmap, in pixels + int sl = pEmr->Src.x + pEmr->xformSrc.eDx; + int st = pEmr->Src.y + pEmr->xformSrc.eDy; + int sw = pEmr->cSrc.x; // extract the specified amount of the image + int sh = pEmr->cSrc.y; + if(sl<0)sl=0; + if(st<0)st=0; + common_image_extraction(d,pEmr,dl,dt,dr,db,sl,st,sw,sh, pEmr->iUsageSrc, pEmr->offBitsSrc, pEmr->cbBitsSrc, pEmr->offBmiSrc, pEmr->cbBmiSrc); } break; @@ -2589,11 +2723,17 @@ std::cout << "BEFORE DRAW" PU_EMRMASKBLT pEmr = (PU_EMRMASKBLT) lpEMFR; // Always grab image, ignore masks and modes. if (pEmr->cbBmiSrc) { - double l = pix_to_x_point( d, pEmr->Dest.x, pEmr->Dest.y); - double t = pix_to_y_point( d, pEmr->Dest.x, pEmr->Dest.y); - double r = pix_to_x_point( d, pEmr->Dest.x + pEmr->cDest.x, pEmr->Dest.y + pEmr->cDest.y); - double b = pix_to_y_point( d, pEmr->Dest.x + pEmr->cDest.x, pEmr->Dest.y + pEmr->cDest.y); - common_image_extraction(d,pEmr,l,t,r,b, + double dl = pix_to_x_point( d, pEmr->Dest.x, pEmr->Dest.y); + double dt = pix_to_y_point( d, pEmr->Dest.x, pEmr->Dest.y); + double dr = pix_to_x_point( d, pEmr->Dest.x + pEmr->cDest.x, pEmr->Dest.y + pEmr->cDest.y); + double db = pix_to_y_point( d, pEmr->Dest.x + pEmr->cDest.x, pEmr->Dest.y + pEmr->cDest.y); + int sl = pEmr->Src.x + pEmr->xformSrc.eDx; //source position within the bitmap, in pixels + int st = pEmr->Src.y + pEmr->xformSrc.eDy; + int sw = 0; // extract all of the image + int sh = 0; + if(sl<0)sl=0; + if(st<0)st=0; + common_image_extraction(d,pEmr,dl,dt,dr,db,sl,st,sw,sh, pEmr->iUsageSrc, pEmr->offBitsSrc, pEmr->cbBitsSrc, pEmr->offBmiSrc, pEmr->cbBmiSrc); } break; @@ -2609,11 +2749,17 @@ std::cout << "BEFORE DRAW" // user can sort out transparency later using Gimp, if need be. PU_EMRSTRETCHDIBITS pEmr = (PU_EMRSTRETCHDIBITS) lpEMFR; - double l = pix_to_x_point( d, pEmr->Dest.x, pEmr->Dest.y ); - double t = pix_to_y_point( d, pEmr->Dest.x, pEmr->Dest.y ); - double r = pix_to_x_point( d, pEmr->Dest.x + pEmr->cDest.x, pEmr->Dest.y + pEmr->cDest.y ); - double b = pix_to_y_point( d, pEmr->Dest.x + pEmr->cDest.x, pEmr->Dest.y + pEmr->cDest.y ); - common_image_extraction(d,pEmr,l,t,r,b, + double dl = pix_to_x_point( d, pEmr->Dest.x, pEmr->Dest.y ); + double dt = pix_to_y_point( d, pEmr->Dest.x, pEmr->Dest.y ); + double dr = pix_to_x_point( d, pEmr->Dest.x + pEmr->cDest.x, pEmr->Dest.y + pEmr->cDest.y ); + double db = pix_to_y_point( d, pEmr->Dest.x + pEmr->cDest.x, pEmr->Dest.y + pEmr->cDest.y ); + int sl = pEmr->Src.x; //source position within the bitmap, in pixels + int st = pEmr->Src.y; + int sw = pEmr->cSrc.x; // extract the specified amount of the image + int sh = pEmr->cSrc.y; + if(sl<0)sl=0; + if(st<0)st=0; + common_image_extraction(d,pEmr,dl,dt,dr,db,sl,st,sw,sh, pEmr->iUsageSrc, pEmr->offBitsSrc, pEmr->cbBitsSrc, pEmr->offBmiSrc, pEmr->cbBmiSrc); dbg_str << "\n"; diff --git a/src/extension/internal/emf-print.cpp b/src/extension/internal/emf-print.cpp index e31c727fb..97600a805 100644 --- a/src/extension/internal/emf-print.cpp +++ b/src/extension/internal/emf-print.cpp @@ -344,10 +344,10 @@ unsigned int PrintEmf::begin (Inkscape::Extension::Print *mod, SPDocument *doc) // dwInchesX x dwInchesY in micrometer units, dpi=90 -> 3543.3 dpm (void) drawing_size((int) ceil(dwInchesX*25.4), (int) ceil(dwInchesY*25.4), 3.543307, &rclBounds, &rclFrame); - // set up the device as A4 horizontal, 47.244094 dpmm (1200 dpi) - int MMX = 216; - int MMY = 279; - (void) device_size(MMX, MMY, 47.244094, &szlDev, &szlMm); // Drawing: A4 horizontal, 42744 dpm (1200 dpi) + // set up the reference device as 100 X A4 horizontal, (1200 dpi/25.4 -> dpmm). Extra digits maintain dpi better in EMF + int MMX = 21600; + int MMY = 27900; + (void) device_size(MMX, MMY, 1200.0/25.4, &szlDev, &szlMm); int PixelsX = szlDev.cx; int PixelsY = szlDev.cy; @@ -381,17 +381,15 @@ unsigned int PrintEmf::begin (Inkscape::Extension::Print *mod, SPDocument *doc) } - // Correct for dpi in EMF vs dpi in Inkscape (always 90?) - // Also correct for the scaling in PX2WORLD, which is set to 20. Doesn't hurt for high resolution, - // helps prevent rounding errors for low resolution EMF. Low resolution EMF is possible if there - // are no print devices and the screen resolution is low. + // Correct for dpi in EMF (1200) vs dpi in Inkscape (always 90). + // Also correct for the scaling in PX2WORLD, which is set to 20. - worldTransform.eM11 = ((float)PixelsX * 25.4f)/((float)MMX*90.0f*PX2WORLD); - worldTransform.eM12 = 0.0f; - worldTransform.eM21 = 0.0f; - worldTransform.eM22 = ((float)PixelsY * 25.4f)/((float)MMY*90.0f*PX2WORLD); - worldTransform.eDx = 0; - worldTransform.eDy = 0; + worldTransform.eM11 = 1200./(90.0*PX2WORLD); + worldTransform.eM12 = 0.0; + worldTransform.eM21 = 0.0; + worldTransform.eM22 = 1200./(90.0*PX2WORLD); + worldTransform.eDx = 0; + worldTransform.eDy = 0; rec = U_EMRMODIFYWORLDTRANSFORM_set(worldTransform, U_MWT_LEFTMULTIPLY); if(!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)){ @@ -785,7 +783,7 @@ int PrintEmf::create_pen(SPStyle const *style, const Geom::Affine &transform) int linecap = 0; int linejoin = 0; uint32_t pen; - uint32_t penStyle; + uint32_t brushStyle; GdkPixbuf *pixbuf; int hatchType; U_COLORREF hatchColor; @@ -803,7 +801,7 @@ int PrintEmf::create_pen(SPStyle const *style, const Geom::Affine &transform) if (!et) return 0; // set a default stroke in case we can't figure out a better way to do it - penStyle = U_BS_SOLID; + brushStyle = U_BS_SOLID; hatchColor = U_RGB(0, 0, 0); hatchType = U_HS_HORIZONTAL; @@ -819,7 +817,7 @@ int PrintEmf::create_pen(SPStyle const *style, const Geom::Affine &transform) height = dheight; brush_classify(pat,0,&pixbuf,&hatchType,&hatchColor); if(pixbuf){ - penStyle = U_BS_DIBPATTERN; + brushStyle = U_BS_DIBPATTERN; rgba_px = (char *) gdk_pixbuf_get_pixels(pixbuf); // Do NOT free this!!! colortype = U_BCBM_COLOR32; (void) RGBA_to_DIB(&px, &cbPx, &ct, &numCt, rgba_px, width, height, width*4, colortype, 0, 1); @@ -830,7 +828,7 @@ int PrintEmf::create_pen(SPStyle const *style, const Geom::Affine &transform) Bmi = bitmapinfo_set(Bmih, ct); } else { // pattern - penStyle = U_BS_HATCHED; + brushStyle = U_BS_HATCHED; if(hatchType == -1){ // Not a standard hatch, so force it to something hatchType = U_HS_CROSS; hatchColor = U_RGB(0xFF,0xC3,0xC3); @@ -838,7 +836,7 @@ int PrintEmf::create_pen(SPStyle const *style, const Geom::Affine &transform) } if(FixPPTPatternAsHatch){ if(hatchType == -1){ // image or unclassified - penStyle = U_BS_HATCHED; + brushStyle = U_BS_HATCHED; hatchType = U_HS_DIAGCROSS; hatchColor = U_RGB(0xFF,0xC3,0xC3); } @@ -886,7 +884,7 @@ int PrintEmf::create_pen(SPStyle const *style, const Geom::Affine &transform) } else if(style->stroke.isColor()){ // test last, always seems to be set, even for other types above sp_color_get_rgb_floatv( &style->stroke.value.color, rgb ); - penStyle = U_BS_SOLID; + brushStyle = U_BS_SOLID; hatchColor = U_RGB(255*rgb[0], 255*rgb[1], 255*rgb[2]); hatchType = U_HS_SOLIDCLR; } @@ -934,7 +932,7 @@ int PrintEmf::create_pen(SPStyle const *style, const Geom::Affine &transform) style->stroke_dash.dash ) { if(FixPPTDashLine){ // will break up line into many smaller lines. Override gradient if that was set, cannot do both. - penStyle = U_BS_SOLID; + brushStyle = U_BS_SOLID; hatchType = U_HS_HORIZONTAL; } else { @@ -959,7 +957,7 @@ int PrintEmf::create_pen(SPStyle const *style, const Geom::Affine &transform) elp = extlogpen_set( U_PS_GEOMETRIC | linestyle | linecap | linejoin, linewidth, - penStyle, + brushStyle, hatchColor, hatchType, n_dash, diff --git a/src/extension/internal/uemf.c b/src/extension/internal/uemf.c index a39c6ad6a..df2739d07 100644 --- a/src/extension/internal/uemf.c +++ b/src/extension/internal/uemf.c @@ -15,7 +15,7 @@ /* File: uemf.c Version: 0.0.9 -Date: 04-OCT-2012 +Date: 24-OCT-2012 Author: David Mathog, Biology Division, Caltech email: mathog@caltech.edu Copyright: 2012 David Mathog and California Institute of Technology (Caltech) @@ -472,7 +472,8 @@ char *U_Utf16leToUtf8( size_t max, size_t *len ){ - char *dst, *dst2, *ret; + char *dst, *dst2; + char *ret=NULL; size_t srclen,dstlen,status; if(max){ srclen = 2*max; } else { srclen = 2*(1 +wchar16len(src)); } //include terminator, length in BYTES @@ -482,10 +483,11 @@ char *U_Utf16leToUtf8( iconv_t conv = iconv_open("UTF-8", "UTF-16LE"); status = iconv(conv, ICONV_CAST &src, &srclen, &dst, &dstlen); iconv_close(conv); - if(status == (size_t) -1)return(NULL); - if(len)*len=strlen(dst2); - ret=U_strdup(dst2); // make a string of exactly the right size - free(dst2); // free the one which was probably too big + if(status != (size_t) -1){ + if(len)*len=strlen(dst2); + ret=U_strdup(dst2); // make a string of exactly the right size + } + free(dst2); // free the one which was probably too big return(ret); } @@ -1074,11 +1076,15 @@ int get_DIB_params( \param ct DIB color table \param numCt DIB color table number of entries \param rgba_px U_RGBA pixel array (32 bits), created by this routine, caller must free. - \param w Width of pixel array - \param h Height of pixel array + \param w Width of pixel array in the record + \param h Height of pixel array in the record \param colortype DIB BitCount Enumeration \param use_ct Kept for symmetry with RGBA_to_DIB, should be set to numCt \param invert If DIB rows are in opposite order from RGBA rows + \param sl start left position in the pixel array in the record to start extracting + \param st start top position in the pixel array in the record to start extracting + \param ew Width of pixel array to extract + \param eh Height of pixel array to extract */ int DIB_to_RGBA( char *px, @@ -1089,7 +1095,11 @@ int DIB_to_RGBA( int h, uint32_t colortype, int use_ct, - int invert + int invert, + int sl, + int st, + int ew, + int eh ){ uint32_t cbRgba_px; int stride; @@ -1103,6 +1113,7 @@ int DIB_to_RGBA( int usedbytes; U_RGBQUAD color; int32_t index; + int ilow,ihigh,rok; // For figuring out OK row when not entire array is converted // sanity checking if(!w || !h || !colortype || !px)return(1); @@ -1110,8 +1121,8 @@ int DIB_to_RGBA( if(!use_ct && colortype < U_BCBM_COLOR16)return(3); //color tables mandatory for < 16 bit if(use_ct && !numCt)return(4); //color table not adequately described - stride = w * 4; - cbRgba_px = stride * h; + stride = ew * 4; + cbRgba_px = stride * eh; bs = colortype/8; if(bs<1){ bs=1; @@ -1121,24 +1132,30 @@ int DIB_to_RGBA( usedbytes = w*bs; } pad = UP4(usedbytes) - usedbytes; // DIB rows must be aligned on 4 byte boundaries, they are padded at the end to accomplish this.; - *rgba_px = (char *) malloc(cbRgba_px); + *rgba_px = (char *) malloc(cbRgba_px); if(!rgba_px)return(4); if(invert){ istart = h-1; iend = -1; iinc = -1; + ihigh = istart - st; + ilow = ihigh - eh + 1; } else { istart = 0; iend = h; iinc = 1; + ilow = st; + ihigh = st + eh -1; } pxptr = px; tmp8 = 0; // silences a compiler warning, tmp8 always sets when j=0, so never used uninitialized for(i=istart; i!=iend; i+=iinc){ - rptr= *rgba_px + i*stride; + if(i>=ilow && i<=ihigh){ rok=1; } + else { rok=0; } + rptr= *rgba_px + (i-ilow)*stride; for(j=0; j=sl && j<=sl+ew-1){ + *rptr++ = r; + *rptr++ = g; + *rptr++ = b; + *rptr++ = a; + } } for(j=0; j 0,29699. \return 0 for success, >=1 for failure. \param xmm Drawing width in millimeters \param ymm Drawing height in millimeters @@ -1622,12 +1642,12 @@ int drawing_size( if(xmm < 0 || ymm < 0 || dpmm < 0)return(1); rclBounds->left = 0; rclBounds->top = 0; - rclBounds->right = U_ROUND((float) xmm * dpmm); // because coordinate system is 0,0 in upper left, N,M in lower right - rclBounds->bottom = U_ROUND((float) ymm * dpmm); + rclBounds->right = U_ROUND((float) xmm * dpmm) - 1; // because coordinate system is 0,0 in upper left, N,M in lower right + rclBounds->bottom = U_ROUND((float) ymm * dpmm) - 1; rclFrame->left = 0; rclFrame->top = 0; - rclFrame->right = U_ROUND((float) xmm * 100.); - rclFrame->bottom = U_ROUND((float) ymm * 100.); + rclFrame->right = U_ROUND((float) xmm * 100.) - 1; + rclFrame->bottom = U_ROUND((float) ymm * 100.) - 1; return(0); } diff --git a/src/extension/internal/uemf.h b/src/extension/internal/uemf.h index 30ed611d6..821827a6d 100644 --- a/src/extension/internal/uemf.h +++ b/src/extension/internal/uemf.h @@ -14,7 +14,7 @@ /* File: uemf.h Version: 0.0.9 -Date: 04-OCT-2012 +Date: 24-OCT-2012 Author: David Mathog, Biology Division, Caltech email: mathog@caltech.edu Copyright: 2012 David Mathog and California Institute of Technology (Caltech) @@ -2701,8 +2701,9 @@ int get_DIB_params( void *pEmr, uint32_t offBitsSrc, uint32_t offBmiSrc, char **px, PU_RGBQUAD *ct, uint32_t *numCt, uint32_t *width, uint32_t *height, uint32_t *colortype, uint32_t *invert ); int DIB_to_RGBA(char *px, PU_RGBQUAD ct, int numCt, - char **rgba_px, int w, int h, uint32_t colortype, int use_ct, int invert); - + char **rgba_px, int w, int h, uint32_t colortype, int use_ct, int invert, + int sl, int st, int ew, int eh ); + int device_size(const int xmm, const int ymm, const float dpmm, U_SIZEL *szlDev, U_SIZEL *szlMm); int drawing_size(const int xmm, const int yum, const float dpmm, U_RECTL *rclBounds, U_RECTL *rclFrame); diff --git a/src/extension/internal/uemf_endian.c b/src/extension/internal/uemf_endian.c index d1563fc82..4fae7f7d4 100644 --- a/src/extension/internal/uemf_endian.c +++ b/src/extension/internal/uemf_endian.c @@ -18,7 +18,7 @@ /* File: uemf_endian.h Version: 0.0.9 -Date: 27-SEP-2012 +Date: 19-SEP-2012 Author: David Mathog, Biology Division, Caltech email: mathog@caltech.edu Copyright: 2012 David Mathog and California Institute of Technology (Caltech) @@ -642,24 +642,52 @@ void U_EMRNOTIMPLEMENTED_swap(char *record, int torev){ // U_EMRHEADER 1 void U_EMRHEADER_swap(char *record, int torev){ + int nDesc,offDesc,nSize,cbPix,offPix; + PU_EMRHEADER pEmr = (PU_EMRHEADER)(record); + if(torev){ + nSize = pEmr->emr.nSize; + } core5_swap(record, torev); + if(!torev){ + nSize = pEmr->emr.nSize; + } - PU_EMRHEADER pEmr = (PU_EMRHEADER)(record); rectl_swap(&(pEmr->rclBounds),2); // rclBounds rclFrame U_swap4(&(pEmr->dSignature), 4); // dSignature nVersion nBytes nRecords U_swap2(&(pEmr->nHandles), 2); // nHandlessReserved + if(torev){ + nDesc = pEmr->nDescription; + offDesc = pEmr->offDescription; + } U_swap4(&(pEmr->nDescription), 3); // nDescription offDescription nPalEntries + if(!torev){ + nDesc = pEmr->nDescription; + offDesc = pEmr->offDescription; + } // UTF16-LE Description sizel_swap(&(pEmr->szlDevice), 2); // szlDevice szlMillimeters - if(torev && pEmr->cbPixelFormat){ - pixelformatdescriptor_swap( (PU_PIXELFORMATDESCRIPTOR) (record + pEmr->offPixelFormat)); - } - U_swap4(&(pEmr->cbPixelFormat), 2); // cbPixelFormat offPixelFormat - if(!torev && pEmr->cbPixelFormat){ - pixelformatdescriptor_swap( (PU_PIXELFORMATDESCRIPTOR) (record + pEmr->offPixelFormat)); + if((nDesc && (offDesc >= 100)) || + (!offDesc && nSize >= 100) + ){ + if(torev){ + cbPix = pEmr->cbPixelFormat; + offPix = pEmr->offPixelFormat; + if(cbPix)pixelformatdescriptor_swap( (PU_PIXELFORMATDESCRIPTOR) (record + pEmr->offPixelFormat)); + } + U_swap4(&(pEmr->cbPixelFormat), 2); // cbPixelFormat offPixelFormat + if(!torev){ + cbPix = pEmr->cbPixelFormat; + offPix = pEmr->offPixelFormat; + if(cbPix)pixelformatdescriptor_swap( (PU_PIXELFORMATDESCRIPTOR) (record + pEmr->offPixelFormat)); + } + U_swap4(&(pEmr->bOpenGL), 1); // bOpenGL + if((nDesc && (offDesc >= 108)) || + (cbPix && (offPix >=108)) || + (!offDesc && !cbPix && nSize >= 108) + ){ + sizel_swap(&(pEmr->szlMicrometers), 1); // szlMicrometers + } } - U_swap4(&(pEmr->bOpenGL), 1); // bOpenGL - sizel_swap(&(pEmr->szlMicrometers), 1); // szlMicrometers } // U_EMRPOLYBEZIER 2 diff --git a/src/extension/internal/uemf_print.c b/src/extension/internal/uemf_print.c index d877c01db..3533090e0 100644 --- a/src/extension/internal/uemf_print.c +++ b/src/extension/internal/uemf_print.c @@ -5,7 +5,7 @@ /* File: uemf_print.c Version: 0.0.9 -Date: 19-SEP-2012 +Date: 19-OCT-2012 Author: David Mathog, Biology Division, Caltech email: mathog@caltech.edu Copyright: 2012 David Mathog and California Institute of Technology (Caltech) @@ -860,15 +860,24 @@ void U_EMRHEADER_print(char *contents, int recnum, size_t off){ printf(" nPalEntries: %d\n", pEmr->nPalEntries ); printf(" szlDevice: {%d,%d} \n", pEmr->szlDevice.cx,pEmr->szlDevice.cy); printf(" szlMillimeters: {%d,%d} \n", pEmr->szlMillimeters.cx,pEmr->szlMillimeters.cy); - printf(" cbPixelFormat: %d\n", pEmr->cbPixelFormat ); - printf(" offPixelFormat: %d\n", pEmr->offPixelFormat); - if(pEmr->cbPixelFormat){ - printf(" PFD:"); - pixelformatdescriptor_print( *(PU_PIXELFORMATDESCRIPTOR) (contents + off + pEmr->offPixelFormat)); - printf("\n"); + if((pEmr->nDescription && (pEmr->offDescription >= 100)) || + (!pEmr->offDescription && pEmr->emr.nSize >= 100) + ){ + printf(" cbPixelFormat: %d\n", pEmr->cbPixelFormat ); + printf(" offPixelFormat: %d\n", pEmr->offPixelFormat); + if(pEmr->cbPixelFormat){ + printf(" PFD:"); + pixelformatdescriptor_print( *(PU_PIXELFORMATDESCRIPTOR) (contents + off + pEmr->offPixelFormat)); + printf("\n"); + } + printf(" bOpenGL: %d\n",pEmr->bOpenGL ); + if((pEmr->nDescription && (pEmr->offDescription >= 108)) || + (pEmr->cbPixelFormat && (pEmr->offPixelFormat >=108)) || + (!pEmr->offDescription && !pEmr->cbPixelFormat && pEmr->emr.nSize >= 108) + ){ + printf(" szlMicrometers: {%d,%d} \n", pEmr->szlMicrometers.cx,pEmr->szlMicrometers.cy); + } } - printf(" bOpenGL: %d\n",pEmr->bOpenGL ); - printf(" szlMicrometers: {%d,%d} \n", pEmr->szlMicrometers.cx,pEmr->szlMicrometers.cy); } // U_EMRPOLYBEZIER 2 -- cgit v1.2.3