From 2d484869a517521f1456076bf0de0b6602ce6cb5 Mon Sep 17 00:00:00 2001 From: Ashish Bhattarai Date: Wed, 18 Mar 2026 17:49:40 +0100 Subject: [PATCH] Fix svg element clipping --- lib/write_svg.js | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/lib/write_svg.js b/lib/write_svg.js index 077838b..8bd3096 100644 --- a/lib/write_svg.js +++ b/lib/write_svg.js @@ -159,6 +159,11 @@ export default function (doc, svg, x, y, options) { white: [NamedColors.white, 1], transparent: [NamedColors.black, 0], }; + const RGBDefaultColors = { + black: [NamedColors.black, 1], + white: [NamedColors.white, 1], + transparent: [NamedColors.black, 0], + }; const Entities = { quot: 34, amp: 38, @@ -694,7 +699,7 @@ export default function (doc, svg, x, y, options) { doc.page.xobjects[group.name] = group.xobj; doc.addContent('/' + group.name + ' Do'); } - function docApplyMask(group, clip) { + function docApplyMask(group) { let name = 'M' + (doc._maskCount = (doc._maskCount || 0) + 1); let gstate = doc.ref({ Type: 'ExtGState', @@ -702,10 +707,8 @@ export default function (doc, svg, x, y, options) { ca: 1, BM: 'Normal', SMask: { - // Needs to be Alpha for clipping to work with cmyk color space - // Clipping mode is reproducing svg clip-path by drawing everything inside clip path with DefaultColors.white [1,1,1,1] - // And everything outside (not draw, defaults to [0,0,0,0]) is masked off - S: clip ? 'Alpha' : 'Luminosity', + // This should ideally be an Alpha mask for clipping but when we set it to Alpha the firefox pdf viewer doesn't render masks correctly + S: 'Luminosity', G: group.xobj, BC: maskBackdrop, }, @@ -850,7 +853,16 @@ export default function (doc, svg, x, y, options) { function docEndText() { doc.addContent('ET'); } - function docFillColor(color) { + function docFillColor(color, isClip) { + if (isClip) { + // always write clips in RGB colorspace + const oldColorProfile = doc._activeColorProfile; + doc._activeColorProfile = null; + doc.fillColor(RGBDefaultColors.white[0], RGBDefaultColors.white[1]); + doc._activeColorProfile = oldColorProfile; + return; + } + if (color[0].constructor.name === 'PDFPattern') { doc.fillOpacity(color[1]); docUsePattern(color[0], false); @@ -2747,7 +2759,7 @@ export default function (doc, svg, x, y, options) { doc.image(image, 0, 0); } else { doc.rect(0, 0, image.width, image.height); - docFillColor(DefaultColors.white).fill(); + docFillColor(DefaultColors.white, true); } doc.restore(); }; @@ -3099,7 +3111,7 @@ export default function (doc, svg, x, y, options) { } } else { this.shape.insertInDocument(); - docFillColor(DefaultColors.white); + docFillColor(DefaultColors.white, true); doc.fill(this.get('clip-rule')); } doc.restore(); @@ -3312,7 +3324,7 @@ export default function (doc, svg, x, y, options) { this.drawChildren(true, false); doc.restore(); docEndGroup(group); - docApplyMask(group, true); + docApplyMask(group); }; }; @@ -3340,7 +3352,7 @@ export default function (doc, svg, x, y, options) { this.drawChildren(false, true); doc.restore(); docEndGroup(group); - docApplyMask(group, false); + docApplyMask(group); }; }; @@ -3412,7 +3424,7 @@ export default function (doc, svg, x, y, options) { } if (fill || stroke || isClip) { if (fill) { - docFillColor(fill); + docFillColor(fill, isClip); } if (stroke && strokeWidth) { docStrokeColor(stroke); @@ -3446,7 +3458,7 @@ export default function (doc, svg, x, y, options) { let fill = this.getFill(isClip, isMask), stroke = this.getStroke(isClip, isMask); if (fill) { - docFillColor(fill); + docFillColor(fill, isClip); } if (stroke) { docStrokeColor(stroke);