Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Custom geometry support (freeform) #872

Closed
wants to merge 1 commit into from

Conversation

apresmoi
Copy link

Hello,

This PR adds support for the custGeom shape. (Freehand, custom polygon, path, etc). This solves #597.

I've implemented this by using a similar spec to the one that uses svg-points.
The path or contour of the custom geometry is declared under the property points of the ShapeProps object.
With this implementation we are supporting all the custom geometry rules: moveTo, lnTo, arcTo, cubicBezTo, quadBezTo and close.

A translation of an svg path to a custom geometry could be achieved by using the svg-points package and adding a custom translation between the arcs.
The svg arc is described by the variables x, y, rx, ry, xAxisRotation, largeArcFlag and sweepFlag. On the other side the pptx freeform arc is described by x, y, hR, wR, stAng, swAng.
In order to add some sort of translation between svg-path and a custom geometry points array we should create a translation between those two representations of the arc.

I took the liberty to add an example of a custom geometry inside the Shape Demos section of the demo file.

Hope you like this.
Thank you !

@gitbrent gitbrent self-assigned this Dec 23, 2020
@apresmoi
Copy link
Author

I've removed the changes outside /src in this PR also.

@apresmoi apresmoi force-pushed the feature/custom_geometry branch from ac9a470 to 3648935 Compare December 24, 2020 12:07
@apresmoi
Copy link
Author

I've fixed the conflicts with the master branch.

@gitbrent gitbrent linked an issue Dec 26, 2020 that may be closed by this pull request
@gitbrent gitbrent added this to the 3.5.0 milestone Dec 26, 2020
@gitbrent gitbrent modified the milestones: 3.5.0, 3.6.0 Mar 31, 2021
@gitbrent gitbrent modified the milestones: 3.6.0, 3.7.0 Apr 27, 2021
@gitbrent gitbrent linked an issue May 5, 2021 that may be closed by this pull request
4 tasks
@gitbrent
Copy link
Owner

Thanks @apresmoi !

Implemented via Pull #984 (clone of this Pull)

Screen Shot 2021-07-19 at 23 31 21

@gitbrent gitbrent closed this Jul 20, 2021
gitbrent added a commit that referenced this pull request Jul 20, 2021
@luizzappa
Copy link

I spent almost 1 week trying to figure out how to convert between svg-path and a custom geometry points array. In case someone needs it, it is necessary to convert from an endpoint parameterization to a center parameterization (as described here https://www.w3.org/TR/SVG11/implnote.html#ArcConversionEndpointToCenter )

I found this article that has this conversion implemented in javascript ( https://observablehq.com/@awhitty/svg-2-elliptical-arc-to-canvas-path2d ). A point of attention is that the function described in the article returns radians and we must work with degrees.

I took the liberty of making some adjustments. The code:

/**
     * Conservion from endpoint to center parameterization
     *
     * @param {Number} x1 absolute coordenate of the x-axis of the current point (x1, y1) on the path 
     * @param {Number} y1 absolute coordenate of the y-axis of the current point (x1, y1) on the path
     * @param {Number} x2 absolute coordenate of the x-axis of the final point (x2, y2) of the arc
     * @param {Number} y2 absolute coordenate of the y-axis of the final point (x2, y2) of the arc
     * @param {Number} largeArcFlag large arc flag. Defines if arc should be greater (fa = 1) or less (fa = 0) than 180 degrees. 
     * @param {Number} sweepFlag sweep flag. Determines if the arc should move at negative angles (fs = 0) or positive angles (fs = 1) 
     * @param {Number} srx the x radius of the arc
     * @param {Number} sry the y radius of the arc
     * @param {Number} xAxisRotationDeg x-axis rotation in degrees. The angle from the x-axis of the current coordinate system to the x-axis of the ellipse
     * 
     * @return {Object} { stAng, swAng } stAng in degrees, swAng in degrees
     */
function arcCenterParameterization(x1, y1, x2, y2, largeArcFlag, sweepFlag, srx, sry, xAxisRotationDeg) {
    //https://observablehq.com/@toja/ellipse-and-elliptical-arc-conversion
    //https://observablehq.com/@awhitty/svg-2-elliptical-arc-to-canvas-path2d

    // console.log('x1', x1, 'y1', y1, 'x2', x2, 'y2', y2, 'largeArcFlag', largeArcFlag, 'sweepFlag', sweepFlag, 'srx', srx, 'sry', sry, 'xAxisRotationDeg', xAxisRotationDeg)

    const xAxisRotation = degToRad(xAxisRotationDeg);

    const cosphi = Math.cos(xAxisRotation);
    const sinphi = Math.sin(xAxisRotation);

    const [x1p, y1p] = mat2DotVec2(
    [cosphi, sinphi, -sinphi, cosphi],
    [(x1 - x2) / 2, (y1 - y2) / 2]
    );

    const [rx, ry] = correctRadii(srx, sry, x1p, y1p);

    const sign = largeArcFlag !== sweepFlag ? 1 : -1;
    const n = pow(rx) * pow(ry) - pow(rx) * pow(y1p) - pow(ry) * pow(x1p);
    const d = pow(rx) * pow(y1p) + pow(ry) * pow(x1p);

    const [cxp, cyp] = vec2Scale(
    [(rx * y1p) / ry, (-ry * x1p) / rx],
    sign * Math.sqrt(Math.abs(n / d))
    );

    // const [cx, cy] = vec2Add(
    //     mat2DotVec2([cosphi, -sinphi, sinphi, cosphi], [cxp, cyp]),
    //     [(x1 + x2) / 2, (y1 + y2) / 2]
    // );

    const a = [(x1p - cxp) / rx, (y1p - cyp) / ry];
    const b = [(-x1p - cxp) / rx, (-y1p - cyp) / ry];
    let startAngle = vec2Angle([1, 0], a);
    const deltaAngle0 = vec2Angle(a, b) % (2 * Math.PI);

    let deltaAngle =
    !sweepFlag && deltaAngle0 > 0
        ? deltaAngle0 - 2 * Math.PI
        : sweepFlag && deltaAngle0 < 0
        ? deltaAngle0 + 2 * Math.PI
        : deltaAngle0;

    let endAngle = startAngle + deltaAngle;

    startAngle = radToDeg(startAngle)
    endAngle = radToDeg(endAngle)
    deltaAngle = radToDeg(deltaAngle)

    // console.log('startAngle', startAngle, 'deltaAngle', deltaAngle, 'endAngle', endAngle)

    return {
    // cx,
    // cy,
    // rx,
    // ry,
    stAng: startAngle,
    swAng: deltaAngle,
    // xAxisRotation,
    // anticlockwise: deltaAngle < 0
    };
}

function correctRadii(signedRx, signedRy, x1p, y1p) {
    const prx = Math.abs(signedRx);
    const pry = Math.abs(signedRy);

    const A = pow(x1p) / pow(prx) + pow(y1p) / pow(pry);

    const rx = A > 1 ? Math.sqrt(A) * prx : prx;
    const ry = A > 1 ? Math.sqrt(A) * pry : pry;

    return [rx, ry];
}

function pow(n) {
    return Math.pow(n, 2);
}

function mat2DotVec2([m00, m01, m10, m11], [vx, vy]) {
    return [m00 * vx + m01 * vy, m10 * vx + m11 * vy];
}

function vec2Add([ux, uy], [vx, vy]) {
    return [ux + vx, uy + vy];
}

function vec2Scale([a0, a1], scalar) {
    return [a0 * scalar, a1 * scalar];
}

function vec2Dot([ux, uy], [vx, vy]) {
    return ux * vx + uy * vy;
}

function vec2Mag([ux, uy]) {
    return Math.sqrt(ux ** 2 + uy ** 2);
}

function vec2Angle(u, v) {
    const [ux, uy] = u;
    const [vx, vy] = v;
    const sign = ux * vy - uy * vx >= 0 ? 1 : -1;
    return sign * Math.acos(vec2Dot(u, v) / (vec2Mag(u) * vec2Mag(v)));
}

function degToRad(deg) {
    return (deg * Math.PI) / 180;
}

function radToDeg(rad) {
    return (rad * 180) / Math.PI
}

export { arcCenterParameterization }

How to use with svg-points? Suppose your points as follows:

points = [ {
  "x": 0,
  "y": -50,
  "moveTo": true
},
{
  "x": 0,
  "y": 50,
  "curve": {
    "type": "arc",
    "rx": 50,
    "ry": 50,
    "sweepFlag": 1,
    "largeArcFlag": 0,
    "xAxisRotation": 0
  }
}]

Just call the function endpointToCenterParameterization with:

x1, y1 ---> x, y of the last point (in this case, x1=0 and y1=-50)
x2, y2 ---> x, y of the current point (x2=0, y2=50)
largeArcFlag --->largeArcFlag
sweepFlag --->sweepFlag
srx --->rx
sry --->ry
xAxisRotationDeg ---> xAxisRotation

hope it helps someone

@niels-bosman
Copy link

@luizzappa Given your example JS code. Can you show an example of how to map a Point[] created from svg-points into a compliant .pptx Point[]?

Which properties should be changed with the endpointToCenterParameterization function?

# for free to join this conversation on GitHub. Already have an account? # to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Question: Is there any way to draw a bell curve shape? Custom polygon generation
4 participants