#!/usr/bin/env node
// This is a command to test the text rendering compliance of OpenType.js.
// It is designed to operate with https://github.com/unicode-org/text-rendering-tests.
//
// Call it like this:
//
//     ./bin/test-render --font=fonts/FiraSansOT-Medium.otf --testcase=TEST-1 --render=BALL
//
// The output will look like this:
//
// <?xml version="1.0" encoding="UTF-8"?>
// <svg version="1.1"
//     xmlns="http://www.w3.org/2000/svg"
//     xmlns:xlink="http://www.w3.org/1999/xlink"
//     viewBox="0 -500 2230 1550">
//   <symbol id="TEST-1.B" overflow="visible"><path d="M443 203C443 117 375 99 309 99L217 99L217 315L315 315C389 315 443 289 443 203ZM417 504C417 436 380 407 305 407L217 407L217 594L299 594C375 594 417 568 417 504ZM581 200C581 312 500 352 432 365L432 369C489 382 552 430 552 515C552 651 429 691 295 691L84 691L84 0L307 0C448 0 581 44 581 200Z"/></symbol>
//   <symbol id="TEST-1.A" overflow="visible"><path d="M452 0L594 0L377 691L214 691L-5 0L133 0L177 160L408 160ZM291 581L295 581L383 260L202 260Z"/></symbol>
//   <symbol id="TEST-1.L" overflow="visible"><path d="M478 0L493 108L217 108L217 691L84 691L84 0Z"/></symbol>
//   <use xlink:href="#TEST-1.B" x="0" y="0"/>
//   <use xlink:href="#TEST-1.A" x="625" y="0"/>
//   <use xlink:href="#TEST-1.L" x="1214" y="0"/>
//   <use xlink:href="#TEST-1.L" x="1722" y="0"/>
// </svg>
//
// When viewing the SVG, it will be upside-down (since glyphs are designed Y-up).

var opentype = require('../dist/opentype.js');

const SVG_FOOTER = `</svg>`;

function printUsage() {
    console.log('Usage: test-render --font=filename.otf --testcase=TEST_NAME --render=TEXT_TO_RENDER');
    console.log('This commands output the text to render as an SVG file.');
    console.log();
}

let filename;
let testcase;
let textToRender;
let variation;
for (let i = 0; i < process.argv.length; i++) {
    const arg = process.argv[i];
    if (arg.startsWith('--font=')) {
        filename = arg.substring('--font='.length);
    } else if (arg.startsWith('--testcase=')) {
        testcase = arg.substring('--testcase='.length);
    } else if (arg.startsWith('--render=')) {
        textToRender = arg.substring('--render='.length);
    } else if (arg.startsWith('--variation=')) {
        variation = {};
        arg.substring('--variation='.length).split(';').forEach(function (setting) {
            var parts = setting.split(':');
            variation[parts[0]] = parseFloat(parts[1]);
        });
    }
}

if (filename === undefined || testcase === undefined || textToRender === undefined) {
  printUsage();
  process.exit(1);
}

// getPath function from fontkit adapted to opentype.js
function getPath(points) {
    const path = new opentype.Path();
    if (!points) {
        return path;
    }
    
    const contours = opentype.Glyph.prototype.getContours.call(this, points);

    for (let i = 0; i < contours.length; i++) {
        let contour = contours[i];
        let firstPt = contour[0];
        let lastPt = contour[contour.length - 1];
        let start = 0;
  
        if (firstPt.onCurve) {
            // The first point will be consumed by the moveTo command, so skip in the loop
            var curvePt = null;
            start = 1;
        } else {
            if (lastPt.onCurve) {
                // Start at the last point if the first point is off curve and the last point is on curve
                firstPt = lastPt;
            } else {
                // Start at the middle if both the first and last points are off curve
                firstPt = {x: (firstPt.x + lastPt.x) / 2, y: (firstPt.y + lastPt.y) / 2, onCurve: falses};
            }

            var curvePt = firstPt;
        }
  
        path.moveTo(firstPt.x, firstPt.y);
  
        for (let j = start; j < contour.length; j++) {
            let pt = contour[j];
            let prevPt = j === 0 ? firstPt : contour[j - 1];

            if (prevPt.onCurve && pt.onCurve) {
                path.lineTo(pt.x, pt.y);
            } else if (prevPt.onCurve && !pt.onCurve) {
                var curvePt = pt;
            } else if (!prevPt.onCurve && !pt.onCurve) {
                let midX = (prevPt.x + pt.x) / 2;
                let midY = (prevPt.y + pt.y) / 2;
                path.quadraticCurveTo(prevPt.x, prevPt.y, midX, midY);
                var curvePt = pt;
            } else if (!prevPt.onCurve && pt.onCurve) {
                path.quadraticCurveTo(curvePt.x, curvePt.y, pt.x, pt.y);
                var curvePt = null;
            } else {
                throw new Error("Unknown TTF path state");
            }
        }
  
        // Connect the first and last points
        if (curvePt) {
          path.quadraticCurveTo(curvePt.x, curvePt.y, firstPt.x, firstPt.y);
        }
  
        path.closePath();
    }

    return path;
}

function renderSVG() {
    var font = opentype.loadSync(filename);

    let svgSymbols = [];
    let svgBody = [];

    var symbolSet = new Set();

    const fontSize = font.unitsPerEm;
    let minWidth = 0;
    if(variation && font.variation) {
        font.variation.set(variation);
    }

    const scale = 1 / font.unitsPerEm * 1000;

    font.forEachGlyph(textToRender, 0, 0, fontSize, {}, function(glyph, gX, gY, gFontSize) {
        let glyphName = glyph.name;
        if(!glyphName || /^cid\d+$/.test(glyphName)) {
            glyphName = `gid${glyph.index}`;
        }
        const 
        symbolId = testcase + '.' + glyphName;

        // TrueType paths from opentype.js are often visually the same as fontkit's, but because
        // of minor differences in path construction, tests will fail.
        // Therefore we re-calculate the path based on the points.
        const pointsTransform = function(points) {
            // Scale to 1000
            for (point of points) {
                point.x *= scale;
                point.y *= scale;
            }
            
            const path = getPath(points);
            return path;
        }

        const pathTransform = function(path) {
            // filter out lines back to the starting point of a contour
            var startX = 0, startY = 0;
            path.commands = path.commands.filter(function (c, i) {
                path.commands[i] = c;
                if (c.type === 'M') {
                    startX = c.x;
                    startY = c.y;
                } else if (c.type === 'L' && (!path.commands[i + 1] || path.commands[i + 1].type === 'Z')) {
                    return Math.abs(c.x - startX) > 1 || Math.abs(c.y - startY) > 1;
                }

                return true;
            });

            return path;
        };

        if (!symbolSet.has(symbolId)) {
            const svgPath = glyph.toSVG({optimize: true, decimalPlaces: 2, flipY: false, pointsTransform, pathTransform}, font)
                // normalize with other renderers
                .replace(/(\d+) (-?\d+)/g, '$1,$2')
                .replace(/(\d)(-)/g, '$1,$2')
                .replace(/(\d)([A-Z])/g, '$1 $2')
                .replace(/Z([^\s])/g, 'Z $1')
                .replace(/\.\d+/g, '');

            svgSymbols.push(`  <symbol id="${symbolId}" overflow="visible">${svgPath}</symbol>`);
            symbolSet.add(symbolId);
        }
        svgBody.push(`  <use xlink:href="#${symbolId}" x="${gX}" y="${gY}"/>`);
        let xMax = gX + glyph.advanceWidth;
        if(xMax > minWidth) {
            minWidth = xMax;
        }

        const glyphWidth = glyph.getMetrics().xMax;
    });

    let minX = 0;
    let minY = Math.round(font.descender);
    let height = Math.round(font.ascender - font.descender);
    let svgHeader = `<?xml version="1.0" encoding="UTF-8"?>
<svg version="1.1"
    xmlns="http://www.w3.org/2000/svg"
    xmlns:xlink="http://www.w3.org/1999/xlink"
    viewBox="${Math.round(minX * scale)} ${Math.round(minY * scale)} ${Math.round(minWidth * scale)} ${Math.round(height * scale)}">`;

    return svgHeader + svgSymbols.join('\n') + svgBody.join('\n') + SVG_FOOTER;
}

try {
    var svg = renderSVG();
    console.log(svg);
} catch(e) {
    console.error(e.stack);
    process.exit(1);
}
