poppler/CairoFontEngine.cc | 11 ++- poppler/CairoOutputDev.cc | 154 ++++++++++++++++++++++++++++++--------------- poppler/CairoOutputDev.h | 21 ++++-- poppler/GfxState.h | 2 4 files changed, 130 insertions(+), 58 deletions(-)
New commits: commit 29f32a477f0f75ee44df890acac377a2eed314c6 Author: Adrian Johnson <[email protected]> Date: Fri Apr 22 16:15:09 2022 +0930 cairo: preserve text color when drawing type 3 glyphs Type 3 glyphs (that don't override the color) should be painted with the current graphics state color when the text operator is invoked. diff --git a/poppler/CairoFontEngine.cc b/poppler/CairoFontEngine.cc index 773c833e..62c4bab6 100644 --- a/poppler/CairoFontEngine.cc +++ b/poppler/CairoFontEngine.cc @@ -567,7 +567,7 @@ static cairo_status_t _render_type3_glyph(cairo_scaled_font_t *scaled_font, unsi box.y2 = mat[3]; gfx = new Gfx(info->doc, output_dev, resDict, &box, nullptr); output_dev->startDoc(info->doc, info->fontEngine); - output_dev->startPage(1, gfx->getState(), gfx->getXRef()); + output_dev->startType3Render(gfx->getState(), gfx->getXRef()); output_dev->setInType3Char(true); charProc = charProcs->getVal(glyph); gfx->display(&charProc); @@ -588,7 +588,11 @@ static cairo_status_t _render_type3_glyph(cairo_scaled_font_t *scaled_font, unsi } status = CAIRO_STATUS_SUCCESS; - if (color && !output_dev->hasColor()) { + + // If this is a render color glyph callback but the Type 3 glyph + // specified non-color, return NOT_IMPLEMENTED. Cairo will then + // call the render non-color glyph callback. + if (color && !output_dev->type3GlyphHasColor()) { status = CAIRO_STATUS_USER_FONT_NOT_IMPLEMENTED; } @@ -624,6 +628,9 @@ CairoType3Font *CairoType3Font::create(const std::shared_ptr<const GfxFont> &gfx font_face = cairo_user_font_face_create(); cairo_user_font_face_set_init_func(font_face, _init_type3_glyph); #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 17, 6) + // When both callbacks are set, Cairo will call the color glyph + // callback first. If that returns NOT_IMPLEMENTED, Cairo will + // then call the non-color glyph callback. cairo_user_font_face_set_render_color_glyph_func(font_face, _render_type3_color_glyph); #endif cairo_user_font_face_set_render_glyph_func(font_face, _render_type3_noncolor_glyph); diff --git a/poppler/CairoOutputDev.cc b/poppler/CairoOutputDev.cc index d51ae4e6..e7dbe463 100644 --- a/poppler/CairoOutputDev.cc +++ b/poppler/CairoOutputDev.cc @@ -137,9 +137,9 @@ CairoOutputDev::CairoOutputDev() fontEngine_owner = false; glyphs = nullptr; fill_pattern = nullptr; - fill_color.r = fill_color.g = fill_color.b = 0; + fill_color = {}; stroke_pattern = nullptr; - stroke_color.r = stroke_color.g = stroke_color.b = 0; + stroke_color = {}; stroke_opacity = 1.0; fill_opacity = 1.0; textClipPath = nullptr; @@ -156,11 +156,10 @@ CairoOutputDev::CairoOutputDev() inUncoloredPattern = false; inType3Char = false; t3_glyph_has_bbox = false; - has_color = false; + t3_glyph_has_color = false; text_matrix_valid = true; groupColorSpaceStack = nullptr; - maskStack = nullptr; group = nullptr; mask = nullptr; shape = nullptr; @@ -281,9 +280,9 @@ void CairoOutputDev::startPage(int pageNum, GfxState *state, XRef *xrefA) cairo_pattern_destroy(stroke_pattern); fill_pattern = cairo_pattern_create_rgb(0., 0., 0.); - fill_color.r = fill_color.g = fill_color.b = 0; + fill_color = { 0, 0, 0 }; stroke_pattern = cairo_pattern_reference(fill_pattern); - stroke_color.r = stroke_color.g = stroke_color.b = 0; + stroke_color = { 0, 0, 0 }; if (textPage) { textPage->startPage(state); @@ -301,6 +300,42 @@ void CairoOutputDev::endPage() } } +void CairoOutputDev::startType3Render(GfxState *state, XRef *xrefA) +{ + /* When cairo calls a user font render function, the default + * source set on the provided cairo_t must be used, except in the + * case of a color user font explicitly setting a color. + * + * As startPage() resets the source to solid black, this function + * is used instead to initialise the CairoOutputDev when rendering + * a user font glyph. + * + * As noted in the Cairo documentation, the default source of a + * render callback contains an internal marker denoting the + * foreground color is to be used when the glyph is rendered, even + * though querying the default source will reveal solid black. + * For this reason, fill_color and stroke_color are set to nullopt + * to ensure updateFillColor()/updateStrokeColor() will update the + * color even if the new color is black. + * + * The saveState()/restoreState() functions also ensure the + * default source is saved and restored, and the fill_color and + * stroke_color is reset to nullopt for the same reason. + */ + + /* Initialise fill and stroke pattern to the current source pattern */ + fill_pattern = cairo_pattern_reference(cairo_get_source(cairo)); + stroke_pattern = cairo_pattern_reference(cairo_get_source(cairo)); + fill_color = {}; + stroke_color = {}; + t3_glyph_has_bbox = false; + t3_glyph_has_color = false; + + if (xrefA != nullptr) { + xref = xrefA; + } +} + void CairoOutputDev::saveState(GfxState *state) { LOG(printf("save\n")); @@ -309,11 +344,20 @@ void CairoOutputDev::saveState(GfxState *state) cairo_save(cairo_shape); } - MaskStack *ms = new MaskStack; - ms->mask = cairo_pattern_reference(mask); - ms->mask_matrix = mask_matrix; - ms->next = maskStack; - maskStack = ms; + /* To ensure the current source, potentially containing the hidden + * foreground color maker, is saved and restored as required by + * _render_type3_glyph, we avoid using the update color and + * opacity functions in restoreState() and instead be careful to + * save all the color related variables that have been set by the + * update functions on the stack. */ + SaveStateElement elem; + elem.fill_pattern = cairo_pattern_reference(fill_pattern); + elem.fill_opacity = fill_opacity; + elem.stroke_pattern = cairo_pattern_reference(stroke_pattern); + elem.stroke_opacity = stroke_opacity; + elem.mask = mask ? cairo_pattern_reference(mask) : nullptr; + elem.mask_matrix = mask_matrix; + saveStateStack.push_back(elem); if (strokePathClip) { strokePathClip->ref_count++; @@ -330,24 +374,26 @@ void CairoOutputDev::restoreState(GfxState *state) text_matrix_valid = true; - /* These aren't restored by cairo_restore() since we keep them in - * the output device. */ - updateFillColor(state); - updateStrokeColor(state); - updateFillOpacity(state); - updateStrokeOpacity(state); + cairo_pattern_destroy(fill_pattern); + fill_pattern = saveStateStack.back().fill_pattern; + fill_color = {}; + fill_opacity = saveStateStack.back().fill_opacity; + + cairo_pattern_destroy(stroke_pattern); + stroke_pattern = saveStateStack.back().stroke_pattern; + stroke_color = {}; + stroke_opacity = saveStateStack.back().stroke_opacity; + + /* This isn't restored by cairo_restore() since we keep it in the + * output device. */ updateBlendMode(state); - MaskStack *ms = maskStack; - if (ms) { - if (mask) { - cairo_pattern_destroy(mask); - } - mask = ms->mask; - mask_matrix = ms->mask_matrix; - maskStack = ms->next; - delete ms; + if (mask) { + cairo_pattern_destroy(mask); } + mask = saveStateStack.back().mask; + mask_matrix = saveStateStack.back().mask_matrix; + saveStateStack.pop_back(); if (strokePathClip && --strokePathClip->ref_count == 0) { delete strokePathClip->path; @@ -533,38 +579,37 @@ void CairoOutputDev::updateLineWidth(GfxState *state) void CairoOutputDev::updateFillColor(GfxState *state) { - GfxRGB color = fill_color; - if (inUncoloredPattern) { return; } - state->getFillRGB(&fill_color); - if (cairo_pattern_get_type(fill_pattern) != CAIRO_PATTERN_TYPE_SOLID || color.r != fill_color.r || color.g != fill_color.g || color.b != fill_color.b) { + GfxRGB new_color; + state->getFillRGB(&new_color); + bool color_match = fill_color && *fill_color == new_color; + if (cairo_pattern_get_type(fill_pattern) != CAIRO_PATTERN_TYPE_SOLID || !color_match) { cairo_pattern_destroy(fill_pattern); - fill_pattern = cairo_pattern_create_rgba(colToDbl(fill_color.r), colToDbl(fill_color.g), colToDbl(fill_color.b), fill_opacity); - - LOG(printf("fill color: %d %d %d\n", fill_color.r, fill_color.g, fill_color.b)); + fill_pattern = cairo_pattern_create_rgba(colToDbl(new_color.r), colToDbl(new_color.g), colToDbl(new_color.b), fill_opacity); + fill_color = new_color; + LOG(printf("fill color: %d %d %d\n", fill_color->r, fill_color->g, fill_color->b)); } - has_color = true; } void CairoOutputDev::updateStrokeColor(GfxState *state) { - GfxRGB color = stroke_color; if (inUncoloredPattern) { return; } - state->getStrokeRGB(&stroke_color); - if (cairo_pattern_get_type(fill_pattern) != CAIRO_PATTERN_TYPE_SOLID || color.r != stroke_color.r || color.g != stroke_color.g || color.b != stroke_color.b) { + GfxRGB new_color; + state->getStrokeRGB(&new_color); + bool color_match = stroke_color && *stroke_color == new_color; + if (cairo_pattern_get_type(fill_pattern) != CAIRO_PATTERN_TYPE_SOLID || !color_match) { cairo_pattern_destroy(stroke_pattern); - stroke_pattern = cairo_pattern_create_rgba(colToDbl(stroke_color.r), colToDbl(stroke_color.g), colToDbl(stroke_color.b), stroke_opacity); - - LOG(printf("stroke color: %d %d %d\n", stroke_color.r, stroke_color.g, stroke_color.b)); + stroke_pattern = cairo_pattern_create_rgba(colToDbl(new_color.r), colToDbl(new_color.g), colToDbl(new_color.b), stroke_opacity); + stroke_color = new_color; + LOG(printf("stroke color: %d %d %d\n", stroke_color->r, stroke_color->g, stroke_color->b)); } - has_color = true; } void CairoOutputDev::updateFillOpacity(GfxState *state) @@ -577,8 +622,13 @@ void CairoOutputDev::updateFillOpacity(GfxState *state) fill_opacity = state->getFillOpacity(); if (opacity != fill_opacity) { + if (!fill_color) { + GfxRGB color; + state->getFillRGB(&color); + fill_color = color; + } cairo_pattern_destroy(fill_pattern); - fill_pattern = cairo_pattern_create_rgba(colToDbl(fill_color.r), colToDbl(fill_color.g), colToDbl(fill_color.b), fill_opacity); + fill_pattern = cairo_pattern_create_rgba(colToDbl(fill_color->r), colToDbl(fill_color->g), colToDbl(fill_color->b), fill_opacity); LOG(printf("fill opacity: %f\n", fill_opacity)); } @@ -594,8 +644,13 @@ void CairoOutputDev::updateStrokeOpacity(GfxState *state) stroke_opacity = state->getStrokeOpacity(); if (opacity != stroke_opacity) { + if (!stroke_color) { + GfxRGB color; + state->getStrokeRGB(&color); + stroke_color = color; + } cairo_pattern_destroy(stroke_pattern); - stroke_pattern = cairo_pattern_create_rgba(colToDbl(stroke_color.r), colToDbl(stroke_color.g), colToDbl(stroke_color.b), stroke_opacity); + stroke_pattern = cairo_pattern_create_rgba(colToDbl(stroke_color->r), colToDbl(stroke_color->g), colToDbl(stroke_color->b), stroke_opacity); LOG(printf("stroke opacity: %f\n", stroke_opacity)); } @@ -607,7 +662,8 @@ void CairoOutputDev::updateFillColorStop(GfxState *state, double offset) return; } - state->getFillRGB(&fill_color); + GfxRGB color; + state->getFillRGB(&color); // If stroke pattern is set then the current fill is clipped // to a stroke path. In that case, the stroke opacity has to be used @@ -615,9 +671,8 @@ void CairoOutputDev::updateFillColorStop(GfxState *state, double offset) // See https://gitlab.freedesktop.org/poppler/poppler/issues/178 auto opacity = (state->getStrokePattern()) ? state->getStrokeOpacity() : state->getFillOpacity(); - cairo_pattern_add_color_stop_rgba(fill_pattern, offset, colToDbl(fill_color.r), colToDbl(fill_color.g), colToDbl(fill_color.b), opacity); - has_color = true; - LOG(printf("fill color stop: %f (%d, %d, %d, %d)\n", offset, fill_color.r, fill_color.g, fill_color.b, dblToCol(opacity))); + cairo_pattern_add_color_stop_rgba(fill_pattern, offset, colToDbl(color.r), colToDbl(color.g), colToDbl(color.b), opacity); + LOG(printf("fill color stop: %f (%d, %d, %d, %d)\n", offset, color.r, color.g, color.b, dblToCol(opacity))); } void CairoOutputDev::updateBlendMode(GfxState *state) @@ -1580,6 +1635,7 @@ void CairoOutputDev::type3D0(GfxState *state, double wx, double wy) { t3_glyph_wx = wx; t3_glyph_wy = wy; + t3_glyph_has_color = true; } void CairoOutputDev::type3D1(GfxState *state, double wx, double wy, double llx, double lly, double urx, double ury) @@ -1591,6 +1647,7 @@ void CairoOutputDev::type3D1(GfxState *state, double wx, double wy, double llx, t3_glyph_bbox[2] = urx; t3_glyph_bbox[3] = ury; t3_glyph_has_bbox = true; + t3_glyph_has_color = false; } void CairoOutputDev::beginTextObject(GfxState *state) { } @@ -2849,7 +2906,6 @@ void CairoOutputDev::drawSoftMaskedImage(GfxState *state, Object *ref, Stream *s cairo_pattern_destroy(maskPattern); cairo_pattern_destroy(pattern); - has_color = true; cleanup: imgStr->close(); @@ -3504,7 +3560,6 @@ void CairoImageOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, i setCairo(nullptr); cairo_surface_destroy(surface); cairo_destroy(cr); - has_color = true; } } @@ -3562,6 +3617,5 @@ void CairoImageOutputDev::drawMaskedImage(GfxState *state, Object *ref, Stream * setCairo(nullptr); cairo_surface_destroy(surface); cairo_destroy(cr); - has_color = true; } } diff --git a/poppler/CairoOutputDev.h b/poppler/CairoOutputDev.h index 4c6fee16..8f64668f 100644 --- a/poppler/CairoOutputDev.h +++ b/poppler/CairoOutputDev.h @@ -230,6 +230,9 @@ public: // Called to indicate that a new PDF document has been loaded. void startDoc(PDFDoc *docA, CairoFontEngine *fontEngine = nullptr); + // Called to prepare this output dev for rendering CairoType3Font. + void startType3Render(GfxState *state, XRef *xref); + bool isReverseVideo() { return false; } void setCairo(cairo_t *cr); @@ -249,7 +252,7 @@ public: } bool hasType3GlyphBBox() { return t3_glyph_has_bbox; } double *getType3GlyphBBox() { return t3_glyph_bbox; } - bool hasColor() { return has_color; } + bool type3GlyphHasColor() { return t3_glyph_has_color; } protected: void doPath(cairo_t *cairo, GfxState *state, const GfxPath *path); @@ -267,7 +270,7 @@ protected: bool setMimeDataForCCITTParams(Stream *str, cairo_surface_t *image, int height); #endif - GfxRGB fill_color, stroke_color; + std::optional<GfxRGB> fill_color, stroke_color; cairo_pattern_t *fill_pattern, *stroke_pattern; double fill_opacity; double stroke_opacity; @@ -317,6 +320,7 @@ protected: bool inType3Char; // inside a Type 3 CharProc double t3_glyph_wx, t3_glyph_wy; bool t3_glyph_has_bbox; + bool t3_glyph_has_color; bool has_color; double t3_glyph_bbox[4]; bool prescaleImages; @@ -338,12 +342,17 @@ protected: struct ColorSpaceStack *next; } * groupColorSpaceStack; - struct MaskStack + struct SaveStateElement { - cairo_pattern_t *mask; + // These patterns hold a reference + cairo_pattern_t *fill_pattern; + cairo_pattern_t *stroke_pattern; + double fill_opacity; + double stroke_opacity; + cairo_pattern_t *mask; // can be null cairo_matrix_t mask_matrix; - struct MaskStack *next; - } * maskStack; + }; + std::vector<SaveStateElement> saveStateStack; }; //------------------------------------------------------------------------ diff --git a/poppler/GfxState.h b/poppler/GfxState.h index b9eb30dc..3ae1550b 100644 --- a/poppler/GfxState.h +++ b/poppler/GfxState.h @@ -179,6 +179,8 @@ typedef GfxColorComp GfxGray; struct GfxRGB { GfxColorComp r, g, b; + + bool operator==(GfxRGB other) const { return r == other.r && g == other.g && b == other.b; } }; //------------------------------------------------------------------------
