//======================================================================== // // CairoOutputDev.cc // // Copyright 2003 Glyph & Cog, LLC // Copyright 2004 Red Hat, Inc // //======================================================================== #include #ifdef USE_GCC_PRAGMAS #pragma implementation #endif #include #include #include #include "goo/gfile.h" #include "GlobalParams.h" #include "Error.h" #include "Object.h" #include "GfxState.h" #include "GfxFont.h" #include "Link.h" #include "CharCodeToUnicode.h" #include "FontEncodingTables.h" #include #include #include "CairoOutputDev.h" #include "CairoFontEngine.h" //------------------------------------------------------------------------ // #define LOG_CAIRO #ifdef LOG_CAIRO #define LOG(x) (x) #else #define LOG(x) #endif //------------------------------------------------------------------------ // CairoOutputDev //------------------------------------------------------------------------ CairoOutputDev::CairoOutputDev() { xref = NULL; FT_Init_FreeType(&ft_lib); fontEngine = NULL; glyphs = NULL; surface = NULL; fill_pattern = NULL; stroke_pattern = NULL; stroke_opacity = 1.0; fill_opacity = 1.0; textClipPath = NULL; } CairoOutputDev::~CairoOutputDev() { if (fontEngine) { delete fontEngine; } FT_Done_FreeType(ft_lib); cairo_surface_destroy (surface); cairo_pattern_destroy (stroke_pattern); cairo_pattern_destroy (fill_pattern); } void CairoOutputDev::setSurface(cairo_surface_t *surface) { cairo_surface_destroy (this->surface); cairo_surface_reference (surface); this->surface = surface; } void CairoOutputDev::startDoc(XRef *xrefA) { xref = xrefA; if (fontEngine) { delete fontEngine; } fontEngine = new CairoFontEngine(ft_lib); } void CairoOutputDev::startPage(int pageNum, GfxState *state) { cairo = cairo_create (surface); } void CairoOutputDev::endPage() { cairo_destroy (cairo); } void CairoOutputDev::drawLink(Link *link, Catalog *catalog) { } void CairoOutputDev::saveState(GfxState *state) { LOG(printf ("save\n")); cairo_save (cairo); } void CairoOutputDev::restoreState(GfxState *state) { LOG(printf ("restore\n")); cairo_restore (cairo); /* These aren't restored by cairo_restore() since we keep them in * the output device. */ updateFillColor(state); updateStrokeColor(state); updateFillOpacity(state); updateStrokeOpacity(state); } void CairoOutputDev::updateAll(GfxState *state) { updateLineDash(state); updateLineJoin(state); updateLineCap(state); updateLineWidth(state); updateFlatness(state); updateMiterLimit(state); updateFillColor(state); updateStrokeColor(state); updateFillOpacity(state); updateStrokeOpacity(state); needFontUpdate = gTrue; } void CairoOutputDev::updateCTM(GfxState *state, double m11, double m12, double m21, double m22, double m31, double m32) { updateLineDash(state); updateLineJoin(state); updateLineCap(state); updateLineWidth(state); } void CairoOutputDev::updateLineDash(GfxState *state) { double *dashPattern; int dashLength; double dashStart; double *transformedDash; double transformedStart; int i; state->getLineDash(&dashPattern, &dashLength, &dashStart); transformedDash = new double[dashLength]; for (i = 0; i < dashLength; ++i) { transformedDash[i] = state->transformWidth(dashPattern[i]); } transformedStart = state->transformWidth(dashStart); cairo_set_dash (cairo, transformedDash, dashLength, transformedStart); delete [] transformedDash; } void CairoOutputDev::updateFlatness(GfxState *state) { // cairo_set_tolerance (cairo, state->getFlatness()); } void CairoOutputDev::updateLineJoin(GfxState *state) { switch (state->getLineJoin()) { case 0: cairo_set_line_join (cairo, CAIRO_LINE_JOIN_MITER); break; case 1: cairo_set_line_join (cairo, CAIRO_LINE_JOIN_ROUND); break; case 2: cairo_set_line_join (cairo, CAIRO_LINE_JOIN_BEVEL); break; } } void CairoOutputDev::updateLineCap(GfxState *state) { switch (state->getLineCap()) { case 0: cairo_set_line_cap (cairo, CAIRO_LINE_CAP_BUTT); break; case 1: cairo_set_line_cap (cairo, CAIRO_LINE_CAP_ROUND); break; case 2: cairo_set_line_cap (cairo, CAIRO_LINE_CAP_SQUARE); break; } } void CairoOutputDev::updateMiterLimit(GfxState *state) { cairo_set_miter_limit (cairo, state->getMiterLimit()); } void CairoOutputDev::updateLineWidth(GfxState *state) { LOG(printf ("line width: %f\n", state->getTransformedLineWidth())); if (state->getTransformedLineWidth() == 0.0) { cairo_set_line_width (cairo, 72.0/300.0); } else { cairo_set_line_width (cairo, state->getTransformedLineWidth()); } } void CairoOutputDev::updateFillColor(GfxState *state) { state->getFillRGB(&fill_color); cairo_pattern_destroy(fill_pattern); fill_pattern = cairo_pattern_create_rgba(fill_color.r / 65535.0, fill_color.g / 65535.0, fill_color.b / 65535.0, fill_opacity); LOG(printf ("fill color: %d %d %d\n", fill_color.r, fill_color.g, fill_color.b)); } void CairoOutputDev::updateStrokeColor(GfxState *state) { state->getStrokeRGB(&stroke_color); cairo_pattern_destroy(stroke_pattern); stroke_pattern = cairo_pattern_create_rgba(stroke_color.r / 65535.0, stroke_color.g / 65535.0, stroke_color.b / 65535.0, stroke_opacity); LOG(printf ("stroke color: %d %d %d\n", stroke_color.r, stroke_color.g, stroke_color.b)); } void CairoOutputDev::updateFillOpacity(GfxState *state) { fill_opacity = state->getFillOpacity(); cairo_pattern_destroy(fill_pattern); fill_pattern = cairo_pattern_create_rgba(fill_color.r / 65535.0, fill_color.g / 65535.0, fill_color.b / 65535.0, fill_opacity); LOG(printf ("fill opacity: %f\n", fill_opacity)); } void CairoOutputDev::updateStrokeOpacity(GfxState *state) { stroke_opacity = state->getStrokeOpacity(); cairo_pattern_destroy(stroke_pattern); stroke_pattern = cairo_pattern_create_rgba(stroke_color.r / 65535.0, stroke_color.g / 65535.0, stroke_color.b / 65535.0, stroke_opacity); LOG(printf ("stroke opacity: %f\n", stroke_opacity)); } void CairoOutputDev::updateFont(GfxState *state) { cairo_font_face_t *font_face; double m11, m12, m21, m22; double w; cairo_matrix_t matrix; LOG(printf ("updateFont() font=%s\n", state->getFont()->getName()->getCString())); needFontUpdate = gFalse; if (state->getFont()->getType() == fontType3) return; currentFont = fontEngine->getFont (state->getFont(), xref); state->getFontTransMat(&m11, &m12, &m21, &m22); m11 *= state->getHorizScaling(); m12 *= state->getHorizScaling(); LOG(printf ("font matrix: %f %f %f %f\n", m11, m12, m21, m22)); font_face = currentFont->getFontFace(); cairo_set_font_face (cairo, font_face); matrix.xx = m11; matrix.xy = -m21; matrix.yx = m12; matrix.yy = -m22; matrix.x0 = 0; matrix.y0 = 0; cairo_set_font_matrix (cairo, &matrix); } void CairoOutputDev::doPath(GfxState *state, GfxPath *path) { GfxSubpath *subpath; double x1, y1, x2, y2, x3, y3; int i, j; for (i = 0; i < path->getNumSubpaths(); ++i) { subpath = path->getSubpath(i); if (subpath->getNumPoints() > 0) { state->transform(subpath->getX(0), subpath->getY(0), &x1, &y1); cairo_move_to (cairo, x1, y1); LOG (printf ("move_to %f, %f\n", x1, y1)); j = 1; while (j < subpath->getNumPoints()) { if (subpath->getCurve(j)) { state->transform(subpath->getX(j), subpath->getY(j), &x1, &y1); state->transform(subpath->getX(j+1), subpath->getY(j+1), &x2, &y2); state->transform(subpath->getX(j+2), subpath->getY(j+2), &x3, &y3); cairo_curve_to (cairo, x1, y1, x2, y2, x3, y3); LOG (printf ("curve_to %f, %f %f, %f %f, %f\n", x1, y1, x2, y2, x3, y3)); j += 3; } else { state->transform(subpath->getX(j), subpath->getY(j), &x1, &y1); cairo_line_to (cairo, x1, y1); LOG(printf ("line_to %f, %f\n", x1, y1)); ++j; } } if (subpath->isClosed()) { LOG (printf ("close\n")); cairo_close_path (cairo); } } } } void CairoOutputDev::stroke(GfxState *state) { doPath (state, state->getPath()); cairo_set_source (cairo, stroke_pattern); LOG(printf ("stroke\n")); cairo_stroke (cairo); } void CairoOutputDev::fill(GfxState *state) { doPath (state, state->getPath()); cairo_set_fill_rule (cairo, CAIRO_FILL_RULE_WINDING); cairo_set_source (cairo, fill_pattern); LOG(printf ("fill\n")); cairo_fill (cairo); } void CairoOutputDev::eoFill(GfxState *state) { doPath (state, state->getPath()); cairo_set_fill_rule (cairo, CAIRO_FILL_RULE_EVEN_ODD); cairo_set_source (cairo, fill_pattern); LOG(printf ("fill-eo\n")); cairo_fill (cairo); } void CairoOutputDev::clip(GfxState *state) { doPath (state, state->getPath()); cairo_set_fill_rule (cairo, CAIRO_FILL_RULE_WINDING); cairo_clip (cairo); LOG (printf ("clip\n")); } void CairoOutputDev::eoClip(GfxState *state) { doPath (state, state->getPath()); cairo_set_fill_rule (cairo, CAIRO_FILL_RULE_EVEN_ODD); cairo_clip (cairo); LOG (printf ("clip-eo\n")); } void CairoOutputDev::beginString(GfxState *state, GooString *s) { int len = s->getLength(); if (needFontUpdate) updateFont(state); glyphs = (cairo_glyph_t *) gmalloc (len * sizeof (cairo_glyph_t)); glyphCount = 0; } void CairoOutputDev::drawChar(GfxState *state, double x, double y, double dx, double dy, double originX, double originY, CharCode code, int nBytes, Unicode *u, int uLen) { double tx, ty; glyphs[glyphCount].index = currentFont->getGlyph (code, u, uLen); state->transform(x, y, &tx, &ty); glyphs[glyphCount].x = tx; glyphs[glyphCount].y = ty; glyphCount++; } void CairoOutputDev::endString(GfxState *state) { int render; if (!currentFont) return; // ignore empty strings and invisible text -- this is used by // Acrobat Capture render = state->getRender(); if (render == 3 || glyphCount == 0) { gfree(glyphs); glyphs = NULL; return; } if (!(render & 1)) { LOG (printf ("fill string\n")); cairo_set_source (cairo, fill_pattern); cairo_show_glyphs (cairo, glyphs, glyphCount); } // stroke if ((render & 3) == 1 || (render & 3) == 2) { LOG (printf ("stroke string\n")); cairo_set_source (cairo, stroke_pattern); cairo_glyph_path (cairo, glyphs, glyphCount); cairo_stroke (cairo); } // clip if (render & 4) { LOG (printf ("clip string\n")); // append the glyph path to textClipPath. // set textClipPath as the currentPath if (textClipPath) { cairo_append_path (cairo, textClipPath); cairo_path_destroy (textClipPath); } // append the glyph path cairo_glyph_path (cairo, glyphs, glyphCount); // move the path back into textClipPath // and clear the current path textClipPath = cairo_copy_path (cairo); cairo_new_path (cairo); } gfree (glyphs); glyphs = NULL; } GBool CairoOutputDev::beginType3Char(GfxState *state, double x, double y, double dx, double dy, CharCode code, Unicode *u, int uLen) { return gFalse; } void CairoOutputDev::endType3Char(GfxState *state) { } void CairoOutputDev::type3D0(GfxState *state, double wx, double wy) { } void CairoOutputDev::type3D1(GfxState *state, double wx, double wy, double llx, double lly, double urx, double ury) { } void CairoOutputDev::endTextObject(GfxState *state) { if (textClipPath) { // clip the accumulated text path cairo_append_path (cairo, textClipPath); cairo_clip (cairo); cairo_path_destroy (textClipPath); textClipPath = NULL; } } void CairoOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str, int width, int height, GBool invert, GBool inlineImg) { unsigned char *buffer; unsigned char *dest; cairo_surface_t *image; cairo_pattern_t *pattern; int x, y; ImageStream *imgStr; Guchar *pix; double *ctm; cairo_matrix_t matrix; int invert_bit; int row_stride; ctm = state->getCTM(); LOG (printf ("drawImageMask %dx%d, matrix: %f, %f, %f, %f, %f, %f\n", width, height, ctm[0], ctm[1], ctm[2], ctm[3], ctm[4], ctm[5])); matrix.xx = ctm[0] / width; matrix.xy = -ctm[2] / height; matrix.yx = ctm[1] / width; matrix.yy = -ctm[3] / height; matrix.x0 = ctm[2] + ctm[4]; matrix.y0 = ctm[3] + ctm[5]; /* work around a cairo bug when scaling 1x1 surfaces */ if (width == 1 && height == 1) { cairo_save (cairo); cairo_set_matrix (cairo, &matrix); cairo_rectangle (cairo, 0., 0., 1., 1.); cairo_fill (cairo); cairo_restore (cairo); return; } row_stride = (width + 3) & ~3; buffer = (unsigned char *) malloc (height * row_stride); if (buffer == NULL) { error(-1, "Unable to allocate memory for image."); return; } /* TODO: Do we want to cache these? */ imgStr = new ImageStream(str, width, 1, 1); imgStr->reset(); invert_bit = invert ? 1 : 0; for (y = 0; y < height; y++) { pix = imgStr->getLine(); dest = buffer + y * row_stride; for (x = 0; x < width; x++) { if (pix[x] ^ invert_bit) *dest++ = 0; else *dest++ = 255; } } image = cairo_image_surface_create_for_data (buffer, CAIRO_FORMAT_A8, width, height, row_stride); if (image == NULL) return; pattern = cairo_pattern_create_for_surface (image); if (pattern == NULL) return; cairo_matrix_invert (&matrix); cairo_pattern_set_matrix (pattern, &matrix); /* we should actually be using CAIRO_FILTER_NEAREST here. However, * cairo doesn't yet do minifaction filtering causing scaled down * images with CAIRO_FILTER_NEAREST to look really bad */ cairo_pattern_set_filter (pattern, CAIRO_FILTER_BEST); /* FIXME: Doesn't the image mask support any colorspace? */ cairo_set_source (cairo, fill_pattern); cairo_mask (cairo, pattern); cairo_pattern_destroy (pattern); cairo_surface_destroy (image); free (buffer); delete imgStr; } void CairoOutputDev::drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, Stream *maskStr, int maskWidth, int maskHeight, GfxImageColorMap *maskColorMap) { ImageStream *maskImgStr; maskImgStr = new ImageStream(maskStr, maskWidth, maskColorMap->getNumPixelComps(), maskColorMap->getBits()); maskImgStr->reset(); int row_stride = (maskWidth + 3) & ~3; unsigned char *maskBuffer; maskBuffer = (unsigned char *)gmalloc (row_stride * maskHeight); unsigned char *maskDest; cairo_surface_t *maskImage; cairo_pattern_t *maskPattern; Guchar *pix; int x, y; for (y = 0; y < maskHeight; y++) { maskDest = (unsigned char *) (maskBuffer + y * row_stride); pix = maskImgStr->getLine(); maskColorMap->getGrayLine (pix, maskDest, maskWidth); } maskImage = cairo_image_surface_create_for_data (maskBuffer, CAIRO_FORMAT_A8, maskWidth, maskHeight, row_stride); delete maskImgStr; maskStr->close(); unsigned char *buffer; unsigned int *dest; cairo_surface_t *image; cairo_pattern_t *pattern; ImageStream *imgStr; GfxRGB rgb; int alpha, i; double *ctm; cairo_matrix_t matrix; int is_identity_transform; buffer = (unsigned char *)gmalloc (width * height * 4); /* TODO: Do we want to cache these? */ imgStr = new ImageStream(str, width, colorMap->getNumPixelComps(), colorMap->getBits()); imgStr->reset(); /* ICCBased color space doesn't do any color correction * so check its underlying color space as well */ is_identity_transform = colorMap->getColorSpace()->getMode() == csDeviceRGB || colorMap->getColorSpace()->getMode() == csICCBased && ((GfxICCBasedColorSpace*)colorMap->getColorSpace())->getAlt()->getMode() == csDeviceRGB; for (y = 0; y < height; y++) { dest = (unsigned int *) (buffer + y * 4 * width); pix = imgStr->getLine(); colorMap->getRGBLine (pix, dest, width); } image = cairo_image_surface_create_for_data (buffer, CAIRO_FORMAT_RGB24, width, height, width * 4); if (image == NULL) return; pattern = cairo_pattern_create_for_surface (image); maskPattern = cairo_pattern_create_for_surface (maskImage); if (pattern == NULL) return; ctm = state->getCTM(); LOG (printf ("drawImageMask %dx%d, matrix: %f, %f, %f, %f, %f, %f\n", width, height, ctm[0], ctm[1], ctm[2], ctm[3], ctm[4], ctm[5])); matrix.xx = ctm[0] / width; matrix.xy = -ctm[2] / height; matrix.yx = ctm[1] / width; matrix.yy = -ctm[3] / height; matrix.x0 = ctm[2] + ctm[4]; matrix.y0 = ctm[3] + ctm[5]; cairo_matrix_invert (&matrix); cairo_pattern_set_matrix (pattern, &matrix); cairo_pattern_set_matrix (maskPattern, &matrix); cairo_pattern_set_filter (pattern, CAIRO_FILTER_BILINEAR); cairo_set_source (cairo, pattern); cairo_mask (cairo, maskPattern); cairo_pattern_destroy (maskPattern); cairo_surface_destroy (maskImage); cairo_pattern_destroy (pattern); cairo_surface_destroy (image); free (buffer); free (maskBuffer); delete imgStr; } void CairoOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, int *maskColors, GBool inlineImg) { unsigned char *buffer; unsigned int *dest; cairo_surface_t *image; cairo_pattern_t *pattern; int x, y; ImageStream *imgStr; Guchar *pix; GfxRGB rgb; int alpha, i; double *ctm; cairo_matrix_t matrix; int is_identity_transform; buffer = (unsigned char *)gmalloc (width * height * 4); /* TODO: Do we want to cache these? */ imgStr = new ImageStream(str, width, colorMap->getNumPixelComps(), colorMap->getBits()); imgStr->reset(); /* ICCBased color space doesn't do any color correction * so check its underlying color space as well */ is_identity_transform = colorMap->getColorSpace()->getMode() == csDeviceRGB || colorMap->getColorSpace()->getMode() == csICCBased && ((GfxICCBasedColorSpace*)colorMap->getColorSpace())->getAlt()->getMode() == csDeviceRGB; if (maskColors) { for (y = 0; y < height; y++) { dest = (unsigned int *) (buffer + y * 4 * width); pix = imgStr->getLine(); colorMap->getRGBLine (pix, dest, width); for (x = 0; x < width; x++) { for (i = 0; i < colorMap->getNumPixelComps(); ++i) { if (pix[i] < maskColors[2*i] * 255|| pix[i] > maskColors[2*i+1] * 255) { *dest = *dest | 0xff000000; break; } } pix += colorMap->getNumPixelComps(); dest++; } } image = cairo_image_surface_create_for_data (buffer, CAIRO_FORMAT_ARGB32, width, height, width * 4); } else { for (y = 0; y < height; y++) { dest = (unsigned int *) (buffer + y * 4 * width); pix = imgStr->getLine(); colorMap->getRGBLine (pix, dest, width); } image = cairo_image_surface_create_for_data (buffer, CAIRO_FORMAT_RGB24, width, height, width * 4); } if (image == NULL) return; pattern = cairo_pattern_create_for_surface (image); if (pattern == NULL) return; ctm = state->getCTM(); LOG (printf ("drawImageMask %dx%d, matrix: %f, %f, %f, %f, %f, %f\n", width, height, ctm[0], ctm[1], ctm[2], ctm[3], ctm[4], ctm[5])); matrix.xx = ctm[0] / width; matrix.xy = -ctm[2] / height; matrix.yx = ctm[1] / width; matrix.yy = -ctm[3] / height; matrix.x0 = ctm[2] + ctm[4]; matrix.y0 = ctm[3] + ctm[5]; cairo_matrix_invert (&matrix); cairo_pattern_set_matrix (pattern, &matrix); cairo_pattern_set_filter (pattern, CAIRO_FILTER_BILINEAR); cairo_set_source (cairo, pattern); cairo_paint (cairo); cairo_pattern_destroy (pattern); cairo_surface_destroy (image); free (buffer); delete imgStr; }