[poppler] poppler/CairoFontEngine.cc poppler/CairoOutputDev.cc poppler/CairoOutputDev.h poppler/GfxState.h
GitLab Mirror
gitlab-mirror at kemper.freedesktop.org
Mon May 9 13:02:17 UTC 2022
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 <ajohnson at redneon.com>
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; }
};
//------------------------------------------------------------------------
More information about the poppler
mailing list