1

I write a converter for my Company from Metafile to SVG (TCanvas->arc). I already finished to convert rectangle or some other elements but i dont get it how i can convert the arc. I write my Code in JavaScript. :)

I have a file and i read it in buffer and get the values but that is uninteresting for you.

So we currently have all the values I can get: Point1,Point2,Start,End

These 4 points are given and from this I should draw an arc now

    dc->Arc (Point1.x + offset->x,
             Point1.y + offset->y, 
             Point2.x + offset->x,
             Point2.y + offset->y,
              Start.x + offset->x,
              Start.y + offset->y, 
              Ende.x + offset->x, 
              Ende.y + offset->y);

They are currently drawing the arc with this command. You can not pay attention to the offset here.

How can i get all Informations from my given points to draw in Arc in SVG.

for Example real values:

         Point1: -50, -6
         Point2: -10, 34
         Start:  -10, 34
         End:    -10, -6 

or

         Point1:  1, 18
         Point2: 41, 58
         Start:  1, 18
         End:    1, 58 

How do I get to the: large-arc-flag, sweep-flag and rotation and what values do I have to use or calculate that it is drawn correctly.

I tried to draw it and looked at a lot of documentation and tried to create it in writing.

Paul LeBeau
  • 97,474
  • 9
  • 154
  • 181
  • You are missing some tags. What is a TCanvas? Are you talking about a Delphi TCanvas? – Paul LeBeau Jun 07 '19 at 18:00
  • 1
    SVG needs the start and end points of the arc. So first you'll need to find the two intersection points between the ellipse and the startline, and between the ellipse and the end line. The rotation will always be 0. TCanvas Arcs always run anti-clockwise, so your sweep flag will always be 0. For the large arc flag you'll need to find the angle between the start and end lines. If it is >180, large arc flag will be 1. – Paul LeBeau Jun 07 '19 at 18:15
  • Where is the Delphi part here? Is it the TCanvas? – Nasreddine Galfout Jun 08 '19 at 01:07

1 Answers1

2

I've whipped up something that seems to work. It's based on the documentation here.

I haven't tested it exhaustively.

I've made the assumption that, in a TCanvas, (0,0) is at the top. If it isn't, you'll need to reverse the logic of the sweep and large arc flags.

var svg = document.querySelector("svg");
var debug = svg.getElementById("debug");


function arc(x1, y1, x2, y2, x3, y3, x4, y4)
{
  let xRadius = Math.abs(x2 - x1) / 2;
  let yRadius = Math.abs(y2 - y1) / 2;
  let xCentre = Math.min(x1, x2) + xRadius;
  let yCentre = Math.min(y1, y2) + yRadius;

  // get intercepts relative to ellipse centre
  let startpt = interceptEllipseAndLine(xRadius, yRadius, x3 - xCentre, y3 - yCentre);
  let endpt = interceptEllipseAndLine(xRadius, yRadius, x4 - xCentre, y4 - yCentre);
  let largeArcFlag = isLargeArc(startpt, endpt) ? 1 : 0;

  return ['M', xCentre + startpt.x, yCentre + startpt.y,
          'A', xRadius, yRadius, 0, largeArcFlag, 0, xCentre + endpt.x, yCentre + endpt.y].join(' ');
}

// Finds the intercept of an ellipse and a line from centre to x0,y0
function interceptEllipseAndLine(xRadius, yRadius, x0,y0)
{
  let den = Math.sqrt(xRadius * xRadius * y0 * y0 + yRadius * yRadius * x0 * x0);
  let mult = xRadius * yRadius / den;
  return {x: mult * x0, y: mult * y0};
}

// Returns true if the angle between the two intercept lines is >= 180deg
function isLargeArc(start, end)
{
  let angle = Math.atan2(start.x * end.y - start.y * end.x, start.x * end.x + start.y * end.y);
  return angle > 0; 
}


let path1 = svg.getElementById("path1");
path1.setAttribute("d", arc(1, 18, 41, 58, 1, 18, 1, 58) );

let path2 = svg.getElementById("path2");
path2.setAttribute("d", arc(-50, -6, -10, 34, -10, 34, -10, -6) );
svg {
  width: 400px;
}

path {
  fill: none;
  stroke: red;
  stroke-width: 1px;
}
<svg viewBox="-100 -100 200 200">
  <path id="path1"/>
  <path id="path2"/>
</svg>

And here's a version that adds some extra shapes for debugging purposes...

var svg = document.querySelector("svg");
var debug = svg.getElementById("debug");


function arc(x1, y1, x2, y2, x3, y3, x4, y4)
{
  let xRadius = Math.abs(x2 - x1) / 2;
  let yRadius = Math.abs(y2 - y1) / 2;
  let xCentre = Math.min(x1, x2) + xRadius;
  let yCentre = Math.min(y1, y2) + yRadius;

  {
    let rect = document.createElementNS(svg.namespaceURI, "rect");
    rect.setAttribute("x", x1);
    rect.setAttribute("y", y1);
    rect.setAttribute("width", x2-x1);
    rect.setAttribute("height", y2-y1);
    debug.append(rect);

    let ellipse = document.createElementNS(svg.namespaceURI, "ellipse");
    ellipse.setAttribute("cx", xCentre);
    ellipse.setAttribute("cy", yCentre);
    ellipse.setAttribute("rx", xRadius);
    ellipse.setAttribute("ry", yRadius);
    debug.append(ellipse);

    let start = document.createElementNS(svg.namespaceURI, "line");
    start.setAttribute("x1", xCentre);
    start.setAttribute("y1", yCentre);
    start.setAttribute("x2", x3);
    start.setAttribute("y2", y3);
    debug.append(start);

    let end = document.createElementNS(svg.namespaceURI, "line");
    end.setAttribute("x1", xCentre);
    end.setAttribute("y1", yCentre);
    end.setAttribute("x2", x4);
    end.setAttribute("y2", y4);
    debug.append(end);
  }

  // get intercepts relative to ellipse centre
  let startpt = interceptEllipseAndLine(xRadius, yRadius, x3 - xCentre, y3 - yCentre);
  let endpt = interceptEllipseAndLine(xRadius, yRadius, x4 - xCentre, y4 - yCentre);
  let largeArcFlag = isLargeArc(startpt, endpt) ? 1 : 0;

  {
    let circ = document.createElementNS(svg.namespaceURI, "circle");
    circ.setAttribute("cx", xCentre + startpt.x);
    circ.setAttribute("cy", yCentre + startpt.y);
    circ.setAttribute("r", 1);
    debug.append(circ);
  }

  return ['M', xCentre + startpt.x, yCentre + startpt.y,
          'A', xRadius, yRadius, 0, largeArcFlag, 0, xCentre + endpt.x, yCentre + endpt.y].join(' ');
}

// Finds the intercept of an ellipse and a line from centre to x0,y0
function interceptEllipseAndLine(xRadius, yRadius, x0,y0)
{
  let den = Math.sqrt(xRadius * xRadius * y0 * y0 + yRadius * yRadius * x0 * x0);
  let mult = xRadius * yRadius / den;
  return {x: mult * x0, y: mult * y0};
}

// Returns true if the angle between the two intercept lines is >= 180deg
function isLargeArc(start, end)
{
  let angle = Math.atan2(start.x * end.y - start.y * end.x, start.x * end.x + start.y * end.y);
  return angle > 0; 
}


let path1 = svg.getElementById("path1");
path1.setAttribute("d", arc(1, 18, 41, 58, 1, 18, 1, 58) );

let path2 = svg.getElementById("path2");
path2.setAttribute("d", arc(-50, -6, -10, 34, -10, 34, -10, -6) );
svg {
  width: 400px;
}

ellipse, rect, line {
  fill: none;
  stroke: lightgrey;
  stroke-width: 0.5px;
}

path {
  fill: none;
  stroke: red;
  stroke-width: 1px;
}
<svg viewBox="-100 -100 200 200">
  <g id="debug"></g>
  
  <path id="path1"/>
  <path id="path2"/>
</svg>

Update: Pie

For the Pie function, it should be almost identical to arc() but it will return a slightly different path.

function pie(x1, y1, x2, y2, x3, y3, x4, y4)
{
  // ... rest of function is the same as arc() ...
  return ['M', xCentre, yCentre,
          'L', xCentre + startpt.x, yCentre + startpt.y,
          'A', xRadius, yRadius, 0, largeArcFlag, 0, xCentre + endpt.x, yCentre + endpt.y,
          'Z'].join(' ');
}
Community
  • 1
  • 1
Paul LeBeau
  • 97,474
  • 9
  • 154
  • 181