fabricjs / fabric.js

Javascript Canvas Library, SVG-to-Canvas (& canvas-to-SVG) Parser
http://fabricjs.com
Other
29.02k stars 3.51k forks source link

Bug Clip Path Inverted on SVG Export #8200

Open neopheus opened 2 years ago

neopheus commented 2 years ago

Version

5.2.1

Test Case

Information about environment

Nodejs or browser? Browser Which browsers? Chrome 104.0.5112.79 (Build officiel) (arm64)

Steps To Reproduce

  1. Go to https://jsfiddle.net/avdigital/uw74h10y/118/ and click on "Export SVG"
Error Message & Stack Trace

```txt ```

Expected Behavior

image

Actual Behavior

image

Code :

var canvas = new fabric.Canvas("c", {});

var clipPathRect = new fabric.Circle({ radius: 50, originX: 'center', originY: 'center' });
clipPathRect.inverted = true;

var myPath = new fabric.Rect({ height: 200, width:300, fill: '#00FF00'});
myPath.clipPath = clipPathRect;
canvas.centerObject(myPath);
canvas.add(myPath);
canvas.renderAll();

function exportToSVG() {
    let w = window.open('')
        let mySVG = canvas.toSVG();
        w.document.write(mySVG)
        console.log('data:image/svg+xml;utf8,' + encodeURIComponent(mySVG));
}

SVG generated :


<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="600"
    height="400" viewBox="0 0 600 400" xml:space="preserve">
    <desc>Created with Fabric.js 5.2.1</desc>
    <defs>
    </defs>
    <g transform="matrix(1 0 0 1 300 200)" clip-path="url(#CLIPPATH_2)">
        <clipPath id="CLIPPATH_2">
            <circle transform="matrix(1 0 0 1 0 0)" cx="0" cy="0" r="50"></circle>
        </clipPath>
        <rect
            style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(0,255,0); fill-rule: nonzero; opacity: 1;"
            x="-150" y="-100" rx="0" ry="0" width="300" height="200"></rect>
    </g>
</svg>

I modified from Inkscape the clip to obtain the desired result

Here is the new SVG :


<?xml version="1.0" encoding="UTF-8"?>
<svg width="600" height="400" version="1.1" viewBox="0 0 600 400" xml:space="preserve"
    xmlns="http://www.w3.org/2000/svg">
    <desc>Created with Fabric.js 5.2.1</desc>
    <defs>
    </defs>
    <g transform="matrix(1 0 0 1 300 200)" clip-path="url(#CLIPPATH_2)">
        <clipPath id="CLIPPATH_2">
            <path class="powerclip"
                d="m-155-105h310v210h-310zm205 105a50 50 0 0 0-50-50 50 50 0 0 0-50 50 50 50 0 0 0 50 50 50 50 0 0 0 50-50z" />
        </clipPath>
        <rect
            style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(0,255,0); fill-rule: nonzero; opacity: 1;"
            x="-150" y="-100" rx="0" ry="0" width="300" height="200"></rect>
    </g>
</svg>

With mask :


<?xml version="1.0" encoding="UTF-8"?>
<svg width="600" height="400" version="1.1" viewBox="0 0 600 400" xml:space="preserve"
    xmlns="http://www.w3.org/2000/svg">
    <desc>Created with Fabric.js 5.2.1</desc>
    <defs>
    </defs>
    <g transform="matrix(1 0 0 1 300 200)" mask="url(#CLIPPATH_2)">
        <mask id="CLIPPATH_2">
            <rect x="-150" y="-100" rx="0" ry="0" width="300" height="200" fill="white"/>
            <circle transform="matrix(1 0 0 1 0 0)" cx="0" cy="0" r="50" fill="black"></circle>
        </mask>
        <rect
            style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(0,255,0); fill-rule: nonzero; opacity: 1;"
            x="-150" y="-100" rx="0" ry="0" width="300" height="200"></rect>
    </g>
</svg>
ShaMan123 commented 2 years ago

the eraser uses mask is definitely better

ShaMan123 commented 2 years ago

7616

neopheus commented 2 years ago

do you have a sample code for this usage?

ShaMan123 commented 2 years ago

https://github.com/fabricjs/fabric.js/blob/af947d69988a189f182f6adc3a338f54468dc90b/src/mixins/eraser_brush.mixin.ts#L97

neopheus commented 2 years ago

Referenc

https://github.com/fabricjs/fabric.js/blob/af947d69988a189f182f6adc3a338f54468dc90b/src/mixins/eraser_brush.mixin.ts#L97

Thx

can we add shapes (rectangles for example) instead of using brushes?

ShaMan123 commented 2 years ago

I don't understand Ahh to the eraser Yes A brush brcomes a path Look into the code

neopheus commented 2 years ago

I don't understand Ahh to the eraser Yes A brush brcomes a path Look into the code

ok

i try

neopheus commented 2 years ago

a proposition : https://jsfiddle.net/avdigital/uw74h10y/180/


fabric.Object.prototype._createBaseSVGMarkup = function(objectMarkup, options) {
    options = options || {};
    var noStyle = options.noStyle,
        reviver = options.reviver,
        styleInfo = noStyle ? '' : 'style="' + this.getSvgStyles() + '" ',
        shadowInfo = options.withShadow ? 'style="' + this.getSvgFilter() + '" ' : '',
        clipPath = this.clipPath,
        vectorEffect = this.strokeUniform ? 'vector-effect="non-scaling-stroke" ' : '',
        absoluteClipPath = clipPath && clipPath.absolutePositioned,
        stroke = this.stroke,
        fill = this.fill,
        shadow = this.shadow,
        commonPieces, markup = [],
        clipPathMarkup,
        // insert commons in the markup, style and svgCommons
        index = objectMarkup.indexOf('COMMON_PARTS'),
        additionalTransform = options.additionalTransform;
    if (clipPath) {
        if (clipPath.inverted) {
            clipPath.clipPathId = 'CLIPPATH_' + fabric.Object.__uid++;
            console.log(this.width);
            clipPathMarkup = '<mask id="' + clipPath.clipPathId + '" >\n';
            clipPathMarkup += '<rect ' +
                ' fill="white"' +
                ' x="' +
                fabric.util.toFixed(-this.width / 2, 2) +
                '" y="' +
                fabric.util.toFixed(-this.height / 2, 2) +
                '" width="' +
                fabric.util.toFixed(this.width, 2) +
                '" height="' +
                fabric.util.toFixed(this.height, 2) +
                '"></rect>\n';
            clipPathMarkup += clipPath.toClipPathSVG(reviver) +
                '</mask>\n';
        } else {
            clipPath.clipPathId = 'CLIPPATH_' + fabric.Object.__uid++;
            clipPathMarkup = '<clipPath id="' + clipPath.clipPathId + '" >\n' +
                clipPath.toClipPathSVG(reviver) +
                '</clipPath>\n';
        }

    }
    if (absoluteClipPath) {
        markup.push('<g ', shadowInfo, this.getSvgCommons(), ' >\n');
    }
    markup.push('<g ', this.getSvgTransform(false), !absoluteClipPath ? shadowInfo + this.getSvgCommons() : '', ' >\n');
    commonPieces = [
        styleInfo,
        vectorEffect,
        noStyle ? '' : this.addPaintOrder(), ' ',
        additionalTransform ? 'transform="' + additionalTransform + '" ' : '',
    ].join('');
    objectMarkup[index] = commonPieces;
    if (fill && fill.toLive) {
        markup.push(fill.toSVG(this));
    }
    if (stroke && stroke.toLive) {
        markup.push(stroke.toSVG(this));
    }
    if (shadow) {
        markup.push(shadow.toSVG(this));
    }
    if (clipPath) {
        markup.push(clipPathMarkup);
    }
    markup.push(objectMarkup.join(''));
    markup.push('</g>\n');
    absoluteClipPath && markup.push('</g>\n');
    return reviver ? reviver(markup.join('')) : markup.join('');
};

fabric.Object.prototype.getSvgCommons = function() {
    var typeClipPath = this.clipPath;
    if (typeClipPath) typeClipPath = this.clipPath.inverted ? 'mask="url(#' : 'clip-path="url(#';
    return [
        this.id ? 'id="' + this.id + '" ' : '',
        typeClipPath ? typeClipPath + this.clipPath.clipPathId + ')" ' : '',
    ].join('');
};