Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using it's URL and the proper URL extention.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <html>
<head>
<script src="https://code.jquery.com/jquery-1.7.2.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.4/raphael-min.js"></script>
  <meta charset="utf-8">
  <title>JS Bin</title>
  <script>
/* 
 * jQuery repeatedclick v1.0.5
 * Dual licensed under the MIT (https://www.opensource.org/licenses/mit-license.php)
 * and GPL (https://www.opensource.org/licenses/gpl-license.php) licenses.
 * Written by: Alexandr Zykov <alexandrz@gmail.com>
 */
if (typeof (jQuery) != "undefined")
  jQuery.fn.hold = function (h, j) {
    var c = jQuery.extend({
      duration: 350,
      speed: 0.85,
      min: 50
    }, j);
    "undefined" === typeof jQuery.repeatedEvents && (jQuery.repeatedEvents = []);
    jQuery.repeatedEvents.push(h);
    var k = jQuery.repeatedEvents.length - 1,
      d, e;
    return this.each(function () {
      d = function (f, b, a) {
        var g = this;
        jQuery.repeatedEvents[f].call(g, a);
        e = setTimeout(function () {
          d.call(g, f, b > c.min ? b * c.speed : b, a)
        }, b)
      };
      jQuery(this).mousedown(function (a) {
        d.call(this, k, c.duration, a)
      });
      var a = function () {
        "undefined" !== typeof e && clearInterval(e)
      };
      jQuery(this).mouseout(a);
      jQuery(this).mouseup(a)
    })
  };
  </script>
  <script>
/*
 * TotalStorage v. 1.1.2
 * Copyright (c) 2012 Jared Novack & Upstatement (upstatement.com)
 * Dual licensed under the MIT and GPL licenses:
 * https://www.opensource.org/licenses/mit-license.php
 * http://www.gnu.org/licenses/gpl.html
 * @author Jared Novack/jared@upstatement.com
 * @url http://upstatement.com/blog/2012/01/jquery-local-storage-done-right-and-easy/
 */
if (typeof (jQuery) != "undefined")
  (function (c) {
    var e = window.localStorage,
      f;
    f = "undefined" == typeof e || "undefined" == typeof window.JSON ? !1 : !0;
    c.totalStorage = function (b, a) {
      return c.totalStorage.impl.init(b, a)
    };
    c.totalStorage.setItem = function (b, a) {
      return c.totalStorage.impl.setItem(b, a)
    };
    c.totalStorage.getItem = function (b) {
      return c.totalStorage.impl.getItem(b)
    };
    c.totalStorage.getAll = function () {
      return c.totalStorage.impl.getAll()
    };
    c.totalStorage.deleteItem = function (b) {
      return c.totalStorage.impl.deleteItem(b)
    };
    c.totalStorage.impl = {
      init: function (b,
        a) {
        return "undefined" != typeof a ? this.setItem(b, a) : this.getItem(b)
      },
      setItem: function (b, a) {
        if (!f) try {
          return c.cookie(b, a), a
        }
        catch (d) {
          console.log("Local Storage not supported by this browser. Install the cookie plugin on your site to take advantage of the same functionality. You can get it at https://github.com/carhartl/jquery-cookie")
        }
        var g = JSON.stringify(a);
        e.setItem(b, g);
        return this.parseResult(g)
      },
      getItem: function (b) {
        if (!f) try {
          return this.parseResult(c.cookie(b))
        }
        catch (a) {
          return null
        }
        return this.parseResult(e.getItem(b))
      },
      deleteItem: function (b) {
        if (!f) try {
          return c.cookie(b, null), !0
        }
        catch (a) {
          return !1
        }
        e.removeItem(b);
        return !0
      },
      getAll: function () {
        var b = [];
        if (f)
          for (d in e) d.length && b.push({
            key: d,
            value: this.parseResult(e.getItem(d))
          });
        else try {
          for (var a = document.cookie.split(";"), d = 0; d < a.length; d++) {
            var g = a[d].split("=")[0];
            b.push({
              key: g,
              value: this.parseResult(c.cookie(g))
            })
          }
        }
        catch (h) {
          return null
        }
        return b
      },
      parseResult: function (b) {
        var a;
        try {
          a = JSON.parse(b), "true" == a && (a = !0), "false" == a && (a = !1), parseFloat(a) == a && "object" != typeof a &&
            (a = parseFloat(a))
        }
        catch (c) {}
        return a
      }
    }
  })(jQuery);
  </script>
  <meta charset=utf-8 />
  <title>Javascript Clipper Main Demo</title>
  <style>
.textarea_hide_buttons {
  margin-bottom: 6px;
  margin-top: 4px;
}
body {
  background-color: #F8F8F8;
}
p {
  margin: 0px;
  padding: 0px;
}
th {
  background-color: #DDDDDD;
  font-weight: bold;
  padding: 0px;
}
body,
th,
td,
input,
legend,
fieldset,
p,
b,
button,
select,
textarea {
  font-size: 13px;
  font-family: Arial, Helvetica, sans-serif;
}
#p,
#_p {
  background-color: #ffffff;
  border: 2px solid #AAAAAA;
}
.svg_mypath {
  stroke: black;
  stroke-width: 1;
  fill: black;
  fill-opacity: 0.3;
}
#p1,
#_p1 {
  fill: #00009C;
  fill-opacity: 0.06;
  stroke: #D3D3DA;
  stroke-opacity: 1.0;
  stroke-width: 0.8;
}
.openpath {
  fill: none !important; // to allow open paths no-fill
}
#p2,
#_p2 {
  fill: #9C0000;
  fill-opacity: 0.06;
  stroke: #FFA07A;
  stroke-opacity: 1.0;
  stroke-width: 0.8;
}
#p3,
#_p3 {
  fill: #80ff9C;
  fill-opacity: 0.5;
  stroke: #003300;
  stroke-opacity: 1.0;
  stroke-width: 0.8;
}
#svg_source_textarea {
  width: 1215px;
  height: 300px;
}
#benchmark_exports_textarea {
  width: 1215px;
  height: 300px;
}
div#svgcontainer {
  margin-right: 2px;
  margin-top: 6px;
}
div#benchmark_div_outer {
  width: 100%;
}
div#benchmark_div {
  width: 100%;
}
#benchmark_fieldset {
  width: 367px;
  min-width: 367px;
  height: 350px;
}
.buttons {
  display: inline-block;
  padding: 0px;
  padding-left: 12px;
  padding-right: 12px;
  background-color: #666666;
  color: white;
  border-radius: 10px;
  text-align: center;
  line-height: 20px;
  font-size: 13px;
  cursor: pointer;
  margin-bottom: 2px;
  margin-top: 2px;
  font-family: Helvetica, Arial, sans-serif;
  white-space: nowrap;
}
span {
  font-weight: normal;
}
.span_title {
  font-weight: bold;
}
LEGEND {
  font-weight: bold;
}
FIELDSET {
  background-color: #EEEEEE;
  /*
        border-radius: 4px 4px 7px 7px; 
        -moz-border-radius: 4px 4px 7px 7px; 
        -webkit-border-radius: 4px 4px 7px 7px; 
        */
  border: 2px solid #AAAAAA;
  white-space: nowrap;
}
table {
  border: 0px;
}
.subpolylinks {
  cursor: pointer;
  font-size: 13px;
  padding: 2px;
}
.subpolylinks: hover {
  background-color: #BBBBBB;
}
.subpolylinks_disabled {
  font-size: 13px;
  padding: 2px;
}
/* To prevent odd text resizing in mobile Safari */

@media only screen and (max-device-width: 480px) {
  .body, p, fieldset, td, th {
    -webkit-text-size-adjust: 100%
  }
}
.polygon_explorer {
  text-align: left;
  border-collapse: collapse;
  white-space: normal;
  width: 461px;
}
.polygon_explorer th {
  font-weight: bold;
}
.polygon_explorer > tr > th,
.polygon_explorer > tr > td,
.polygon_explorer > tbody > tr > th,
.polygon_explorer > tbody > tr > td {
  border: 1px solid #666666;
  padding: 3px 4px 3px 4px;
  vertical-align: top;
}
.polygons_fieldset {
  border-collapse: collapse;
  width: 100%;
}
.polygons_fieldset td {
  vertical-align: top;
  white-space: nowrap;
  padding: 0px;
  padding-right: 10px;
}
.polygons_fieldset input[type=radio] {
  margin-left: 0px;
}
.random {
  border-collapse: collapse;
  width: 100%;
}
.random td {
  padding: 0px;
  padding-right: 10px;
}
.random_inputs {
  width: 25px
}
.transform_inputs {
  width: 27px
}
.random th {
  padding: 0px;
  padding-right:
}
.random th {
  background-color: transparent;
}
.random_new_th {
  background-color: transparent;
}
.random_new_th button {
  margin: 0px;
}
.random button,
.random input {
  margin-bottom: 0px;
  margin-top: 0px;
}
#subj_polygon_count {
  margin-bottom: 2px;
}
.filltype_cliptype {
  border-collapse: collapse;
}
.filltype_cliptype td {
  vertical-align: top;
  padding: 0px;
}
.offset_outer {
  border-collapse: collapse;
  width: 100%;
}
.offset_outer td {
  vertical-align: top;
  white-space: nowrap;
  padding: 0px;
}
.offset_outer input[type=radio] {
  margin-left: 0px;
}
.offset_inner input[type=text] {
  margin-bottom: 2px;
}
.offset_inner {
  border-collapse: collapse;
}
.offset_inner td {
  vertical-align: middle;
  height: 100%;
  padding-right: 2px;
}
#custom_polygons_fieldset {
  display: none;
}
#random_polygons_fieldset {
  display: none;
}
#misc_fieldset {
  min-height: 81px;
}
.custom_polygon_input {
  width: 285px;
  height: 15px;
  padding: 2px;
  line-height: 15px;
}
#polygon_explorer_string_inp {
  margin: 0px;
  height: 15px;
  width: 323px;
  padding: 2px;
  line-height: 15px;
}
#custom_polygon_inputs {
  display: block;
}
#custom_poly_message_box_green {
  display: none;
  opacity: 0;
  position: absolute;
  top: 0px;
  left: 0px;
  height: 0px;
  width: 200px;
  vertical-align: middle;
  text-align: center;
  border: 2px solid #106712;
  background-color: #A3F1A5;
  padding: 8px;
  padding-left: 11px;
  padding-right: 11px;
  border-radius: 6px;
  font-size: 16px;
  z-index: 2;
}
.custom_polygon_inputs_table {
  border-collapse: collapse;
}
#help_builtin_polygon_sets,
#help_output_format,
#help_custom_polygon {
  padding-left: 7px;
  padding-right: 7px;
}

  </style>
</head>
<body onload="window.num = 0">

  <table id="outertable" style="margin-bottom:5px">
    <tr>
      <td id="td0" style="vertical-align:top;width:353px;min-width:353px">
        <FIELDSET id="PolygonsFieldset">
          <LEGEND title="Builtin polygons">Polygons</LEGEND>
          <table class="polygons_fieldset">
            <tr>
              <td>
                <input type="radio" name="polygons" value="0">Arrows
                <br>
                <input type="radio" name="polygons" value="1">Texts
                <br>
                <input type="radio" name="polygons" value="2">Rects
                <br>
              </td>
              <td>
                <input type="radio" name="polygons" value="3">ZigZag
                <br>
                <input type="radio" name="polygons" value="4" title="Random rectangles">RandRects
                <br>
                <input type="radio" name="polygons" value="5" title="Random polygons">Random
                <br>
              </td>
              <td>
                <input type="radio" name="polygons" value="11" title="Random Grids">RandGrid
                <br>
                <input type="radio" name="polygons" value="6">Star & rect
                <br>
                <input type="radio" name="polygons" value="7">Spiral
                <br>
              </td>
              <td style="padding-right:0px">
                <input type="radio" name="polygons" value="8">Grid & star
                <br>
                <input type="radio" name="polygons" value="9">Glyph
                <br>
                <input type="radio" name="polygons" value="10" title="Your own custom polygon sets">Custom
                <br>
              </td>
            </tr>
          </table>
        </FIELDSET>
        <FIELDSET id="custom_polygons_fieldset" style="position:relative">
          <LEGEND>Your own custom polygons</LEGEND>
          <select name="custom_polygons_select" id="custom_polygons_select" title="Saved custom polygon sets"></select>
          <select name="sample_custom_polygon" id="sample_custom_polygon" title="Get Builtin polygon set">
            <option value="">- Builtin -</option>
            <option value="0">Arrows</option>
            <option value="1">Texts</option>
            <option value="2">Rects</option>
            <option value="3">ZigZag</option>
            <option value="4">RandRects</option>
            <option value="5">Random</option>
            <option value="6">Star & rect</option>
            <option value="7">Spiral</option>
            <option value="8">Grid & star</option>
            <option value="9">Glyph</option>
            <option value="12">Collinear test</option>
            <option value="13">Spiral & strips</option>
          </select>
          <button id="help_builtin_polygon_sets" title="Get help about Builtin polygon sets">?</button>
          <div id="custom_polygon_inputs">
            <table class="custom_polygon_inputs_table">
              <tr>
                <td>
                  <b title="Here you can type or paste custom subject polygons. Click the '?' button below to get help about string formats.">Subj</b>
                </td>
                <td>
                  <textarea class="custom_polygon_input" id="custom_polygon_subj" value=''></textarea>
                </td>
              </tr>
              <tr>
                <td>
                  <b title="Here you can type or paste custom clip polygons. Click the '?' button below to get help about string formats.">Clip</b>
                </td>
                <td>
                  <textarea class="custom_polygon_input" id="custom_polygon_clip" value=''></textarea>
                </td>
              </tr>
            </table>
            <button id="save_custom_polygon" title="Update polygon set">Save</button>
            <button id="add_as_new_custom_polygon" title="Save as a new custom polygon set">Save as new</button>
            <button id="remove_custom_polygon" title="Remove selected custom polygon set">Del</button>
            <button id="removeall_custom_polygon" title="Remove all custom polygon sets">Del all</button>
            <button id="help_custom_polygon" title="Get help about custom polygons">?</button>
          </div>
          <div id="custom_poly_message_box_green"></div>
        </FIELDSET>
        <FIELDSET id="random_polygons_fieldset">
          <LEGEND>Random polygon settings</LEGEND>
          <table class="random">
            <thead>
              <tr>
                <th class="random_new_th">
                  <button id="generate_random_polygons" title="Generate random polygons">New</button>
                </th>
                <th>Polygon count</th>
                <th style="padding-right:0px">Point count</th>
              </tr>
            </thead>
            <tbody>
              <tr>
                <td><b>Subject</b>
                </td>
                <td>
                  <button id="subj_polygon_count_minus">&nbsp;-&nbsp;</button>
                  <input class="random_inputs" type="text" id="subj_polygon_count" value="1">
                  <button id="subj_polygon_count_plus">&nbsp;+&nbsp;</button>
                </td>
                <td style="padding-right:0px">
                  <button id="subj_point_count_minus">&nbsp;-&nbsp;</button>
                  <input class="random_inputs" type="text" id="subj_point_count" value="1">
                  <button id="subj_point_count_plus">&nbsp;+&nbsp;</button>
                </td>
              </tr>
              <tr>
                <td><b>Clip</b>
                </td>
                <td>
                  <button id="clip_polygon_count_minus">&nbsp;-&nbsp;</button>
                  <input class="random_inputs" type="text" id="clip_polygon_count" value="1">
                  <button id="clip_polygon_count_plus">&nbsp;+&nbsp;</button>
                </td>
                <td style="padding-right:0px">
                  <button id="clip_point_count_minus">&nbsp;-&nbsp;</button>
                  <input class="random_inputs" type="text" id="clip_point_count" value="1">
                  <button id="clip_point_count_plus">&nbsp;+&nbsp;</button>
                </td>
              </tr>
            </tbody>
          </table>
        </FIELDSET>

        <FIELDSET id="random_grids_fieldset">
          <LEGEND>Random grid settings</LEGEND>
          <table class="random_grid_table">
            <thead>
              <tr>
                <th class="random_new_th"></th>
                <th>Grid Type</th>
              </tr>
            </thead>
            <tbody>
              <tr>
                <td class="random_new_th">
                  <button id="generate_random_grids" title="Generate random grids">New</button>
                </td>
                <td>
                  <button id="random_grid_type_minus">&nbsp;-&nbsp;</button>
                  <input class="random_inputs" type="text" id="random_grid_type" value="1">
                  <button id="random_grid_type_plus">&nbsp;+&nbsp;</button>
                </td>
              </tr>
            </tbody>
          </table>
        </FIELDSET>

        <FIELDSET id="transform_fieldset">
          <LEGEND>Transform</LEGEND>
          <table class="transforms_table">
            <thead>
              <tr>
                <th style="text-align:center;"><b title="Rotate paths around center">Rotate</b>
                </th>
                <th style="text-align:center;"><b title="Make perspective distort to paths">Distort</b>
                </th>
              </tr>
            </thead>
            <tbody>
              <tr>
                <td style="padding-right:0px">
                  <button id="transform_rotation_minus">&nbsp;-&nbsp;</button>
                  <input class="transform_inputs" type="text" id="transform_rotation" value="0">
                  <button id="transform_rotation_plus">&nbsp;+&nbsp;</button>
                </td>

                <td style="padding-right:0px">
                  <button id="transform_distort" title="Generate new random Distort">New</button>
                  <button id="transform_distort_sample1" title="Distort Sample 1">S1</button>
                  <button id="transform_distort_sample2" title="Distort Sample 2">S2</button>
                </td>
                <td style="padding-right:0px">
                  <button id="transform_reset"><span title="Remove transformations">Reset</span>
                  </button>
                </td>
              </tr>
            </tbody>
          </table>
        </FIELDSET>

        <table id="filltype_cliptype_table" class="filltype_cliptype" style="width:100%">
          <tr>
            <td>
              <FIELDSET>
                <LEGEND>Subject FillType</LEGEND>
                <input type="radio" name="subject_fillType" value="0">EvenOdd&nbsp;
                <input type="radio" name="subject_fillType" value="1">NonZero</FIELDSET>
            </td>
            <td>
              <FIELDSET>
                <LEGEND>Clip FillType</LEGEND>
                <input type="radio" name="clip_fillType" value="0">EvenOdd&nbsp;
                <input type="radio" name="clip_fillType" value="1">NonZero</FIELDSET>
            </td>
          </tr>
        </table>
        <FIELDSET id="ClipTypeFieldset">
          <LEGEND>Clip type (operation)</LEGEND>
          <p>
            <input type="radio" name="clipType" value="">No&nbsp;&nbsp;
            <input type="radio" name="clipType" value="0">Intersect&nbsp;&nbsp;
            <input type="radio" name="clipType" value="1">Union&nbsp;&nbsp;
            <input type="radio" name="clipType" value="2">Difference&nbsp;&nbsp;
            <input type="radio" name="clipType" value="3" checked>Xor</p>
        </FIELDSET>
        <FIELDSET id="CleaningFieldset">
          <LEGEND>Cleaning , Simplifying, Lightening and Parameters</LEGEND>
          <p title="">
            <b>
            <span class="span_title" title="Clean. Removes vertices: A) that join co-linear edges, or join edges that are almost co-linear (such that if the vertex was moved no more than the specified distance the edges would be co-linear), B) that are within the specified distance of an adjacent vertex (this is needed for preventing polygon brekage when offsetting), C) that are within the specified distance of a semi-adjacent vertex together with their out-lying vertices. Cleaning is done before offsetting.">Clean</span>
            </b>
            <input type="checkbox" id="clean" value="1">
            <input style="width:34px" type="text" value="" id="cleandelta" title="Delta. The distance used for Clean. Sequential vertices are joined if they are at or below this distance of each other.">&nbsp;&nbsp;

            <b><span class="span_title" title="Simplify. Convert complex (self-intersecting) polygon to simple ones. Produces multiple polygons, if there are self-intersections. Note! Not work in self-intersections that are too small, you have to remove too-near-vertices using Clean feature. This operation is done before offsetting.">Simplify</span></b>
            <input type="checkbox" id="Simplify" value="1">&nbsp;&nbsp;

            <b><span class="span_title" title="Lighten. Removes unnecessary vertices. Not included in original Clipper. This operation is done after offsetting.">Lighten</span></b>
            <input type="checkbox" id="lighten" value="1">
            <input style="width:34px;" type="text" value="" id="lighten_distance" title="Tolerance. The distance that is used when lightening polygon. Removes middle vertex of three sequential vertices, if the middle vertex is under (or at) this distance of the line between start and end vertex.">
            <br>
            <b><span class="span_title" title="PreserveCollinear. Prevents the removal of 'inner' vertices when three or more vertices are collinear in solution paths.">PreserveCollinear</span></b>
            <input type="checkbox" id="PreserveCollinear" value="1">&nbsp;&nbsp;

            <b><span class="span_title" title="ReverseSolution. When this property is set to true, polygons returned in the solution parameter of the Execute() method will have orientations opposite to their normal orientations.">Reverse</span></b>
            <input type="checkbox" id="ReverseSolution" value="1">&nbsp;&nbsp;

            <b><span class="span_title" title="StrictlySimple. Force polygons returned in the solution parameter of the Execute() method to be splitted into simple polygons.">ToSimple</span></b>
            <input type="checkbox" id="StrictlySimple" value="1">&nbsp;&nbsp;
          </p>
        </FIELDSET>
        <FIELDSET id="OffsettingFieldset">
          <LEGEND title="Offsetting polygons with positive or negative amount and offsetting open paths (lines).">Offsetting</LEGEND>
          <table class="offset_outer">
            <tr>
              <td colspan="3">
                <table style="width:100%;border-spacing: 0; margin:0px; margin-bottom:4px;border-bottom:1px dashed #AAA">
                  <tr>
                    <td style="padding-bottom:3px">
                      <b style="display:inline"><span class="span_title" style="display:inline;padding-bottom:3px" title="How start and end of path are represented">EndType</span></b>&nbsp;&nbsp;&nbsp;
                      <input type="radio" name="endType" value="0"><span title="OpenSquare">OpenSquare</span>&nbsp;&nbsp;
                      <input type="radio" name="endType" value="1"><span title="OpenRound">OpenRound</span>&nbsp;&nbsp;
                      <input type="radio" name="endType" value="2"><span title="OpenButt">OpenButt</span>
                      <br>
                      <input type="radio" name="endType" value="3"><span title="ClosedLine">ClosedLine</span>&nbsp;&nbsp;
                      <input type="radio" name="endType" value="4"><span title="ClosedPolygon">ClosedPolygon</span>
                    </td>
                  </tr>
                </table>

              </td>
            </tr>
            <tr>
              <td style="padding-right:6px;">
                <b><span class="span_title" style="display:block;padding-bottom:3px" title="Which of poly types is used for offsetting">PolyType</span></b>
                <input type="radio" name="offsettable_poly" value="1">
                <span title="Subject polygon is used for offsetting">Subject</span>
                <br>
                <input type="radio" name="offsettable_poly" value="2">
                <span title="Clip polygon is used for offsetting">Clip</span>
                <br>
                <input type="radio" id="offsettable_poly3" name="offsettable_poly" value="3" checked>
                <span title="Solution polygon (clip operation result) is used for offsetting">Solution</span>
              </td>
              <td style="padding-right:6px;">
                <b><span class="span_title" style="display:block;padding-bottom:3px" title="Edge join type">JoinType</span></b>
                <input type="radio" name="joinType" value="0" checked>
                <span title="Convex edge joins are squared off at exactly delta units.">Square</span>
                <br>
                <input type="radio" name="joinType" value="1">
                <span title="Convex edge joins are rounded off at exactly delta units.">Round</span>
                <br>
                <input type="radio" name="joinType" value="2">
                <span title="Convex edge joins are squared off only when a new vertex would exceed limit * delta distance from the initial vertex location.">Miter</span>
                <br>
              </td>
              <td style="padding-right:0px">
                <table class="offset_inner">
                  <!--
                  <tr>
                    <td style="padding-right:6px;">
                      <b><span class="span_title" style="display:block;padding-bottom:3px" title="">Closed</span></b>
                    </td>
                    <td>
                      <input type="checkbox" id="off_poly_closed" name="off_poly_closed" value="1" checked>
                    </td>
                  </tr>
-->
                  <tr>
                    <td style="padding-right: 6px;">
                      <b id="LimitTitle" title="MiterLimit sets the maximum distance in multiples of delta that vertices can be offset from their original positions before squaring is applied.">Mlimit</b>
                    </td>
                    <td>
                      <button id="miterLimit_minus">&nbsp;-&nbsp;</button>
                    </td>
                    <td>
                      <input type="text" id="miterLimit" value="" style="width:50px">
                    </td>
                    <td>
                      <button id="miterLimit_plus">&nbsp;+&nbsp;</button>
                    </td>

              </td>
              </tr>
              <tr>
                <td style="padding-right: 6px;">
                  <b id="arcToleranceTitle" title="ArcTolerance specifies the maximum distance flattened path will deviate from the 'true' arc. Only relevant when JoinType is jtRound and/or EndType is etRound.">AToler</b>
                </td>
                <td>
                  <button id="arcTolerance_minus">&nbsp;-&nbsp;</button>
                </td>
                <td>
                  <input type="text" id="arcTolerance" value="" style="width:50px">
                </td>
                <td>
                  <button id="arcTolerance_plus">&nbsp;+&nbsp;</button>
                </td>
      </td>
      </tr>
      <tr>
        <td style="padding-right: 6px;">
          <b title="Amount of offset. Paths may be open or closed paths. With closed paths (polygons), positive delta values 'expand' outer contours and 'shrink' inner 'hole' contours. Negative deltas do the reverse. With open paths (lines) negative values have no effect.">Delta</b>
          <td>
            <button disabled id="delta_minus">&nbsp;-&nbsp;</button>
          </td>
          <td>
            <input disabled type="text" id="delta" value="" style="width:50px;font-weight:bold">
          </td>
          <td>
            <button disabled id="delta_plus">&nbsp;+&nbsp;</button>
          </td>
        </td>
      </tr>

      </table>
      </td>
      </tr>
      </table>
      </FIELDSET>
      <FIELDSET id="ScaleFieldset">
        <LEGEND>Scale</LEGEND>
        <p style="width:200px;">
          <button id="scale_minus">&nbsp;-&nbsp;</button>
          <input type="text" id="scale" value="" style="width:80px">
          <button id="scale_plus">&nbsp;+&nbsp;</button> <span id="scaletest"></span>&nbsp; <b>Bigints used:</b>
          <span id="biginteger_used"></span>
        </p>
      </FIELDSET>
      <FIELDSET id="misc_fieldset">
        <LEGEND>Misc</LEGEND>
        <button id="show_svg_source" title="Show current SVG and it's source in textarea at the bottom of the page">Show SVG, Bottom</button>
        &nbsp;<b>Bevel</b> 
        <input type="checkbox" id="bevel" value="1" title="Uncheck this to speedup drawing">&nbsp;&nbsp;
        <b>Explorer</b> 
        <input type="checkbox" id="sub_poly_links_update" value="1" title="Enable/disable Polygon Explorer">
        <br>
        <button id="benchmark1" title="Execute Normal benchmark">Run NB</button>
        <button id="benchmark1b" title="Execute Normal benchmark 5x">Run NB 5x</button>
        <button id="benchmark2" title="Execute Big Integer benchmark">Run BIB</button>
        <button id="benchmark2b" title="Execute Big Integer benchmark 5x">Run BIB 5x</button>
      </FIELDSET>
      </td>
      <td id="td1" style="vertical-align:top;width:506px;">
        <div id="svgcontainer"></div>
        <FIELDSET id="polygon_explorer_fieldset">
          <LEGEND>Polygon explorer</LEGEND>
          <div id="polygon_explorer_div" style="padding-left:1px;/*overflow-y:auto;overflow-x:visible;max-height:300px*/">
            <table class="polygon_explorer">
              <tr>
                <td colspan="4" style="padding:0px;white-space:nowrap; border:0px">
                  <table style="padding:0px;border-collapse:collapse">
                    <tr>
                      <td style="padding:0px;vertical-align:middle">
                        <textarea id="polygon_explorer_string_inp" title="When you click the numbers below, here comes coordinates of the clicked polygon (or polygons) as string." value="">Click below to see coordinates here!</textarea>
                      </td>
                      <td style="padding:0px;vertical-align:middle">
                        <select id="output_format" title="Output format" style="width:73px;margin-left:5px">
                          <option value="0">Clipper</option>
                          <option value="1">Plain</option>
                          <option value="2">SVG</option>
                        </select>&nbsp;&nbsp;<b>Ex</b> 
                        <input type="checkbox" id="ExPolygons" value="1" title="Show solution as ExPolygons">
                      </td>
                    </tr>
                  </table>
                </td>
              </tr>
              <tr>
                <th style="white-space:nowrap; width:31px;" title="Polygon type">Type</th>
                <th style="white-space:nowrap; width:35px;" title="Count of subpolygons">Polys</th>
                <th style="white-space:nowrap; width:40px;" title="Count of points in all sub polygons. Hover numbers below to see the whole multi-polygon highlighted. Click them to see coordinates as strings and the area of multi-polygon (sum of areas of sub polygons).">Points</th>
                <th style="white-space:nowrap;" title="Count of points in sub polygons. Hover numbers below to see sub polygons highlighted. Click to see coordinates as strings and the area of sub polygon.">Points in subpolygons</th>
              </tr>
              <tr>
                <td id="subject_box" title="Subject">
                  <b>Subj</b>
                </td>
                <td>
                  <span id="subj_subpolygons"></span>
                </td>
                <td>
                  <span id="subj_points_total" class="subpolylinks" onclick="popup_path(undefined,'1')" onmouseover="show_path(undefined,'1')" onmouseout="hide_path()"></span>
                </td>
                <td>
                  <span id="subj_points_in_subpolygons"></span>
                </td>
              </tr>
              <tr>
                <td id="clip_box" title="Clip">
                  <b>Clip</b>
                </td>
                <td>
                  <span id="clip_subpolygons">
	                </span>
                </td>
                <td><span id="clip_points_total" class="subpolylinks" onclick="popup_path(undefined,'2')" onmouseover="show_path(undefined,'2')" onmouseout="hide_path()"></span>
                </td>
                <td>
                  <span id="clip_points_in_subpolygons"></span>
                </td>
              </tr>
              <tr>
                <td id="solution_box" title="Solution">
                  <b>Solu</b>
                </td>
                <td>
                  <span id="solution_subpolygons"></span>
                </td>
                <td>
                  <span id="solution_points_total" class="subpolylinks" onclick="popup_path(undefined,'3',solution_points_total)" onmouseover="show_path(undefined,'3',solution_points_total)" onmouseout="hide_path()"></span>
                </td>
                <td>
                  <span id="solution_points_in_subpolygons">
	                </span>
                </td>
              </tr>
              <tr>
                <td>
                  <b>Total</b>
                </td>
                <td>
                  <b><span id="all_subpolygons"></span></b>
                </td>
                <td>
                  <b><span id="points_total"></span></b>
                </td>
                <td style="padding-bottom:0px;padding-top:0px;text-align:right;white-space:nowrap">
                  <div id="area" style="padding:0px;display:inline-block;width:90%;text-align:left" title="Click above numbers to see the area of sub polygons.">Click above to see some polygon metrics!</div>
                  <button id="help_output_format" title="Get help about output format">?</button>
                </td>
              </tr>
            </table>
            <div id="debug">Debug:<br></div>
          </div>
        </FIELDSET>
      </td>
      <td id="td2" style="width:353px;vertical-align:top;">
        <FIELDSET id="benchmark_fieldset_f" style="min-height:517px">
          <LEGEND id="#benchmark_fieldset">Benchmark</LEGEND>
          <div id="benchmark_div_outer">
            <div id="benchmark_div"></div>
            <div id="benchmark_multiple_table_cont"></div>
          </div>
        </FIELDSET>
      </td>
      <td id="td3" style="vertical-align:top;">
        <div id="svg_source_container2"></div>
      </td>
    </tr>
  </table>
  <div id="svg_source_container"></div>
  <div id="benchmark_exports_container"></div>
  <script>
window.rectangle1 =
'[[{"X":155,"Y":61.67},{"X":155,"Y":212.7},{"X":288.98,"Y":212.7},{"X":288.98,"Y":61.67},{"X":173.01999999999998,"Y":61.67},{"X":155,"Y":61.67}],[{"X":274.37,"Y":190.77},{"X":171.24,"Y":190.77},{"X":171.24,"Y":81.16},{"X":274.37,"Y":81.16},{"X":274.37,"Y":81.16},{"X":274.37,"Y":190.77}]]';
window.rectangle2 =
'[[{"X":217.69,"Y":126.14},{"X":217.69,"Y":277.17},{"X":351.66999999999996,"Y":277.17},{"X":351.66999999999996,"Y":126.14},{"X":253.71,"Y":126.14},{"X":217.69,"Y":126.14}],[{"X":337.05,"Y":255.25},{"X":233.93,"Y":255.25},{"X":233.93,"Y":145.63},{"X":337.05,"Y":145.63},{"X":337.05,"Y":145.63},{"X":337.05,"Y":255.25}]]';
  
//var scale = 10000000000000;
var scale = 1;
var global_do_not_round_and_scale = false;
var scale_addition = 100;
var polygons_default = 2;
var selected_polygon_set = 2;
var joinType = 2;
var endType = 4;
var miterLimit = 2.0;
var arcTolerance = 0.25;
var delta = 1;
var AutoFix = false;
var ExPolygons = false;
var SolutionPolygonClicked = false;
var SolutionPolygonString = "";
var Simplify = false;
var clean = false;
var cleandelta_default = 0.1;
var cleandelta = cleandelta_default;
var lighten = false;
var lighten_distance_default = 0.1;
var lighten_distance = lighten_distance_default;
var PreserveCollinear = false;
var ReverseSolution = false;
var StrictlySimple = false;
var off_poly_closed = true;
var subj_is_closed = true;
var offsettable_poly = 3;
var clipType = 0;
var clipType_default = 0;
var subject_fillType = 1;
var clip_fillType = 1;
var ss, cc, sss, cpr, p, ok = 1;
var ss_copy;
var cc_copy;
var ss_transformed;
var cc_transformed;
var off_result;
var SVG = {}, p, p1, p2, p3;
var random_subj;
var random_clip;
var random_grid_subj;
var rnd_sett = {};
var rnd_sett_defaults = {};
rnd_sett_defaults.rect = {};
rnd_sett_defaults.norm = {};
rnd_sett_defaults.rect.
default = {
  clip_polygon_count: 1,
  clip_point_count: 4,
  subj_polygon_count: 2,
  subj_point_count: 8
};
rnd_sett_defaults.rect.min = {
  clip_polygon_count: 1,
  clip_point_count: 4,
  subj_polygon_count: 1,
  subj_point_count: 4
};
rnd_sett_defaults.rect.max = {
  clip_polygon_count: 100,
  clip_point_count: 100,
  subj_polygon_count: 100,
  subj_point_count: 100
};
rnd_sett_defaults.norm.
default = {
  clip_polygon_count: 1,
  clip_point_count: 3,
  subj_polygon_count: 2,
  subj_point_count: 8
};
rnd_sett_defaults.norm.min = {
  clip_polygon_count: 1,
  clip_point_count: 3,
  subj_polygon_count: 1,
  subj_point_count: 3
};
rnd_sett_defaults.norm.max = {
  clip_polygon_count: 100,
  clip_point_count: 100,
  subj_polygon_count: 100,
  subj_point_count: 100
};
rnd_sett_defaults.current = "norm";
var rnd_grid_sett = {
  random_grid_type: 2
};
var transform = {
  rotation: 0,
  distort: null,
  distort_destination: null,
  destination1: [
    {
      X: 100 * scale,
      Y: 10 * scale
    },
    {
      X: 400 * scale,
      Y: 10 * scale
    },
    {
      X: 490 * scale,
      Y: 340 * scale
    },
    {
      X: 10 * scale,
      Y: 340 * scale
    }
  ],
  destination2: [
    {
      X: 50 * scale,
      Y: 10 * scale
    },
    {
      X: 400 * scale,
      Y: 100 * scale
    },
    {
      X: 490 * scale,
      Y: 250 * scale
    },
    {
      X: 10 * scale,
      Y: 340 * scale
    }
  ]
};
var bevel = 0;
var mypath = null;
var scaled_paths = [];
var subj_points_total = 0;
var clip_points_total = 0;
var solution_points_total = 0;
var subj_subpolygons = 0;
var clip_subpolygons = 0;
var solution_subpolygons = 0;
var bench;
var sub_poly_links_update = 1;
var benchmark1_globals = {};
var browserg;
var repeat_times;
var repeat = 0;
var clicked_benchmark_button_id;
var benchmark_exports = "";
var benchmark_running = 0;
var benchmark_automatic_click = 0;
// default_custom_subject_polygon = '[[{"X":90.58,"Y":144.44},{"X":84.83,"Y":141.55},{"X":84.84,"Y":141.56},{"X":79.29,"Y":137.67},{"X":73.79,"Y":132.61},{"X":73.8,"Y":132.62},{"X":67.31,"Y":125.14},{"X":63.22,"Y":119.64},{"X":98.93,"Y":130.63},{"X":98.91,"Y":130.61},{"X":99.02,"Y":130.66},{"X":99.02,"Y":130.65},{"X":97.46,"Y":134.4},{"X":93.93,"Y":139.6},{"X":90.86,"Y":144.22},{"X":90.87,"Y":144.21}]]';
// The Phantom image
var default_custom_subject_polygon = window.rectangle1
var default_custom_clip_polygon = window.rectangle2;
var output_format = 1;
var ClipperLib_MaxSteps_original; // this hold the original ClipperLib.MaxSteps value
// during benchmarks, ClipperLib.MaxSteps is set to 10
// to make benchmarks comparable
var ismousedown = false; // Not yet in use
var bench_glob = [];
var bench_elapsed_time = 0;
window.onerror = function (message, url, linenumber) {
  console.log("JavaScript error: " + message + " on line " + linenumber + " for " + url);
}


function get_browser() {
  var nav = navigator.userAgent.toString().toLowerCase();
  var browser = {};
  if (nav.indexOf("chrome") != -1 && nav.indexOf("chromium") == -1) browser.chrome = 1;
  else browser.chrome = 0;
  if (nav.indexOf("chromium") != -1) browser.chromium = 1;
  else browser.chromium = 0;
  if (nav.indexOf("safari") != -1 && nav.indexOf("chrome") == -1 && nav.indexOf("chromium") == -1) browser.safari = 1;
  else browser.safari = 0;
  if (nav.indexOf("firefox") != -1) browser.firefox = 1;
  else browser.firefox = 0;
  if (nav.indexOf("firefox/17") != -1) browser.firefox17 = 1;
  else browser.firefox17 = 0;
  if (nav.indexOf("firefox/15") != -1) browser.firefox15 = 1;
  else browser.firefox15 = 0;
  if (nav.indexOf("firefox/3") != -1) browser.firefox3 = 1;
  else browser.firefox3 = 0;
  if (nav.indexOf("opera") != -1) browser.opera = 1;
  else browser.opera = 0;
  if (nav.indexOf("msie 10") != -1) browser.msie10 = 1;
  else browser.msie10 = 0;
  if (nav.indexOf("msie 9") != -1) browser.msie9 = 1;
  else browser.msie9 = 0;
  if (nav.indexOf("msie 8") != -1) browser.msie8 = 1;
  else browser.msie8 = 0;
  if (nav.indexOf("msie 7") != -1) browser.msie7 = 1;
  else browser.msie7 = 0;
  if (nav.indexOf("msie ") != -1) browser.msie = 1;
  else browser.msie = 0;
  for (var i in browser) {
    if (browser[i] === 1 && !i.match(/[0-9]/)) browser["browser"] = i;
  }
  browser.version = $.browser.version;
  return browser;
}
/* Normalizes inputted polygon string
       Input can be JSON-stringified version of the following:
       - Array of arrays of points [[{"X":10,"Y":10},{"X":10,"Y":10},{"X":10,"Y":10}][{"X":10,"Y":10},{"X":10,"Y":10},{"X":10,"Y":10}]]
       - Array of points [{"X":10,"Y":10},{"X":10,"Y":10},{"X":10,"Y":10}]
       - The aboves in lowercase
       - Array of x,y-coordinates eg. [0, 10, 20, 30, 40, 50] or [0 10 20 30 40 50] or [0 10, 20 30, 40 50];
       - The above without []
       - SVG path string with commands MLVHZ and mlvhz
       Returns normalized Clipper Polygons object stringified or false in failure
      */
function normalize_clipper_poly(polystr, quiet) {
  if (typeof (polystr) != "string") return false;
  polystr = polystr.trim();
  var np, txt;
  if (polystr.substr(0, 1).toUpperCase() === "M") {
    np = svgpath_to_clipper_polygons(polystr);
    if (np === false) {
      txt = "Unable to parse SVG path string.\n";
      txt += "Click OK to continue.\n";
      if (!quiet) alert(txt);
      return false;
    }
    else return JSON.stringify(np);
  }
  polystr = polystr.replace(/[\s,]+/g, ",");
  if (polystr.substr(0, 1) !== "[") polystr = "[" + polystr;
  if (polystr.substr(-1, 1) !== "]") polystr = polystr + "]";
  try {
    polystr = polystr.replace(/X/gi, '"X"').replace(/Y/gi, '"Y"').replace(/""/g, '"'); // if " is missing
    var poly = JSON.parse(polystr);
  }
  catch (err) {
    txt = "Unable to parse polygon string.\n";
    txt += "Error: " + err.message + "\n";
    txt += "Click OK to continue.\n";
    if (!quiet) alert(txt);
    return false;
  }
  // if only points without "X" and "Y"
  var temp_n = [],
    i;
  if (isArray(poly) && poly.length && typeof (poly[0]) == "number") {
    var len = poly.length;
    for (i = 0; i < len; i = i + 2) {
      temp_n.push({
        X: poly[i],
        Y: poly[i + 1]
      });
    }
    poly = temp_n;
  }
  // if an array of array of points without "X" and "Y"
  var temp_n2 = [],
    i, j, len, len2;
  if (isArray(poly) && poly.length && isArray(poly[0]) && typeof (poly[0][0]) != "undefined" &&
    typeof (poly[0][0].X) == "undefined" &&
    typeof (poly[0][0].x) == "undefined") {
    len2 = poly.length;
    for (j = 0; j < len2; j++) {
      temp_n = [];
      len = poly[j].length;
      for (i = 0; i < len; i = i + 2) {
        temp_n.push({
          X: poly[j][i],
          Y: poly[j][i + 1]
        });
      }
      temp_n2.push(temp_n);
    }
    poly = temp_n2;
  }
  // if not array of arrays, convert to array of arrays
  if (isArray(poly) && poly.length > 0 && !isArray(poly[0])) poly = [poly];
  var pp, n = [
      []
    ],
    m, pm, x, y;
  np = [
    []
  ];
  for (i = 0, m = poly.length; i < m; i++) {
    np[i] = [];
    for (j = 0, pm = poly[i].length; j < pm; j++) {
      pp = {};
      y = null;
      x = null;
      if (typeof (poly[i][j].X) != "undefined" && !isNaN(Number(poly[i][j].X))) x = Number(poly[i][j].X);
      else if (typeof (poly[i][j].x) != "undefined" && !isNaN(Number(poly[i][j].x))) x = Number(poly[i][j].x);
      if (typeof (poly[i][j].Y) != "undefined" && !isNaN(Number(poly[i][j].Y))) y = Number(poly[i][j].Y);
      else if (typeof (poly[i][j].y) != "undefined" && !isNaN(Number(poly[i][j].y))) y = Number(poly[i][j].y);
      if (y !== null && x !== null) {
        pp.X = x;
        pp.Y = y;
        np[i].push(pp);
      }
      else {
        txt = "Unable to parse polygon string.\n";
        txt += "Error: Coordinates are not in a right form.\n";
        txt += "Click OK to continue.\n";
        if (!quiet) alert(txt);
        return false;
      };
    }
  }
  return JSON.stringify(np);
}
// helper function for normalize_clipper_poly()
function svgpath_to_clipper_polygons(d) {
  var arr;
  d = d.trim();
  arr = Raphael.parsePathString(d); // str to array
  arr = Raphael._pathToAbsolute(arr); // mahvstcsqz -> uppercase
  var str = Array.prototype.concat.apply([], arr).join(" ");
  var paths = str.replace(/M/g, '|M').split("|");
  var k, polygons_arr = [],
    polygon_arr = [];
  for (k = 0; k < paths.length; k++) {
    if (paths[k].trim() === "") continue;
    arr = Raphael.parsePathString(paths[k].trim());
    polygon_arr = [];
    var i = 0,
      j, m = arr.length,
      letter = "",
      x = 0,
      y = 0,
      pt = {},
      subpath_start = {};
    subpath_start.x = "";
    subpath_start.y = "";
    for (; i < m; i++) {
      letter = arr[i][0].toUpperCase();
      if (letter != "M" && letter != "L" && letter != "Z") continue;
      if (letter != "Z") {
        for (j = 1; j < arr[i].length; j = j + 2) {
          if (letter == "V") y = arr[i][j];
          else if (letter == "H") x = arr[i][j];
          else {
            x = arr[i][j];
            y = arr[i][j + 1];
          }
          pt = {};
          pt.X = null;
          pt.Y = null;
          if (typeof (x) != "undefined" && !isNaN(Number(x))) pt.X = Number(x);
          if (typeof (y) != "undefined" && !isNaN(Number(y))) pt.Y = Number(y);
          if (pt.X !== null && pt.Y !== null) {
            polygon_arr.push(pt);
          }
          else {
            return false;
          }
        }
      }
      if ((letter != "Z" && subpath_start.x === "") || letter == "M") {
        subpath_start.x = x;
        subpath_start.y = y;
      }
      if (letter == "Z") {
        x = subpath_start.x;
        y = subpath_start.y;
      }
    }
    polygons_arr.push(polygon_arr);
  }
  return polygons_arr;
}

function format_output(polystr, ExPolygonsOrNot) {
  var txt, returnAsExPolygons = false;
  if (typeof (polystr) != "string" || polystr === "") return "";
  if (ExPolygonsOrNot == "ExPolygons" && ExPolygons) returnAsExPolygons = true;
  try {
    var poly = JSON.parse(polystr);
  }
  catch (err) {
    txt = "Unable to parse polygon for output.\n";
    txt += "Error: " + err.message + "\n";
    txt += "Click OK to continue.\n";
    alert(txt);
    return "";
  }
  var i, j, m, n, newpolystr = "";
  if (output_format === 0) // Clipper
  {
    //return polystr;
  }
  else if (output_format == 1) // Plain
  {
    m = poly.length;
    for (i = 0; i < m; i++) {
      newpolystr += "[";
      n = poly[i].length;
      for (j = 0; j < n; j++) {
        newpolystr += poly[i][j].X + "," + poly[i][j].Y;
        if (j !== n - 1) newpolystr += ", ";
      }
      newpolystr += "]";
      if (i !== m - 1) newpolystr += ",";
    }
    //return "[" + newpolystr + "]";
    polystr = "[" + newpolystr + "]";
  }
  else if (output_format == 2) // SVG
  {
    m = poly.length;
    for (i = 0; i < m; i++) {
      n = poly[i].length;
      for (j = 0; j < n; j++) {
        if (j === 0) newpolystr += "M";
        else newpolystr += "L";
        newpolystr += poly[i][j].X + "," + poly[i][j].Y;
        if (j !== n - 1) newpolystr += " ";
      }
      newpolystr += "Z";
      if (i !== m - 1) newpolystr += " ";
    }
    if (newpolystr.trim() == "Z") newpolystr = "";
    //return newpolystr;
    polystr = newpolystr;
  }
  if (!returnAsExPolygons) return polystr;
  else {
    if (!cpr) cpr = new ClipperLib.Clipper();
    else cpr.Clear();
    cpr.AddPaths(poly, ClipperLib.PolyType.ptSubject, subj_is_closed);
    sss = new ClipperLib.PolyTree();
    cpr.Execute(ClipperLib.ClipType.ctUnion, sss, subject_fillType, clip_fillType);
    var solution_Polygons = new ClipperLib.Paths();
    var solution_exPolygons = ClipperLib.JS.PolyTreeToExPolygons(sss);
    polystr = JSON.stringify(solution_exPolygons);
    sss = [];
    return polystr;
  }
  /*
    var solution_polygons_tree = new ClipperLib.PolyTree();
    var solution_polygons = new ClipperLib.Paths();
    cpr.Execute(clipTypes[i], solution_polygons_tree, subject_fillType, clip_fillType);
//cpr.Execute(clipTypes[i], solution_polygons, subject_fillType, clip_fillType);
        
    var solution_exPolygons = new ClipperLib.ExPolygons();
    ClipperLib.PolyTreeToExPolygons(solution_polygons_tree, solution_exPolygons);
    //ClipperLib.PolyTreeToExPolygons(solution_polygons, solution_exPolygons);
//console.log(i);
ClipperLib.Clipper.PolyTreeToPolygons(solution_polygons_tree, solution_polygons);

*/
  /*
        else if (output_format == 3) // ExPolygons
        {
          m = poly.length;
          for(i = 0; i < m; i++)
          {
            newpolystr += "[";
            n = poly[i].length;
            for(j = 0; j < n; j++)
            {
              newpolystr += poly[i][j].X + "," + poly[i][j].Y;
              if (j !== n - 1) newpolystr += ", ";
            }   
            newpolystr += "]";
            if (i !== m - 1) newpolystr += ",";
          }
          return "[" + newpolystr + "]";
        }
*/
};

function get_gb_and_arrow() {
  var gb_poly = window.rectangle1;
    var arrow_poly = window.rectangle2;
      return {
    "ss": deserialize_clipper_poly(gb_poly),
    "cc": deserialize_clipper_poly(arrow_poly)
  }
}

function get_text_polys() {
  var text_polygon = window.rectangle1;
  var text_clipping = window.rectangle2;   
  return {
    "ss": deserialize_clipper_poly(text_polygon),
    "cc": deserialize_clipper_poly(text_clipping)
  };
}

function get_rectangle_polys() {
  var rectangle1 = window.rectangle1;
  var rectangle2 = window.rectangle2;
  return {
    "ss": deserialize_clipper_poly(rectangle1),
    "cc": deserialize_clipper_poly(rectangle2)
  };
}

function get_ZigZag_polys() {
  var rectangle1 = window.rectangle1;
  var rectangle2 = '[]';
  return {
    "ss": deserialize_clipper_poly(rectangle1),
    "cc": deserialize_clipper_poly(rectangle2)
  };
}

function get_star_and_rect() {
  var star = '[[{"X":147.47,"Y":312.74},{"X":247.07,"Y":33.510000000000005},{"X":337.66,"Y":312.04},{"X":86.36,"Y":122.7},{"X":404.07,"Y":123.57},{"X":148.11,"Y":312.27},{"X":147.47,"Y":312.74}]]';
  var rectangle1 = '[[{"X":336.36,"Y":261.38},{"X":155.25,"Y":260.49},{"X":155.25,"Y":84.06},{"X":336.36,"Y":84.06},{"X":336.36,"Y":261.38},{"X":336.36,"Y":261.38}]]';
  return {
    "ss": deserialize_clipper_poly(star),
    "cc": deserialize_clipper_poly(rectangle1)
  };
}

function get_spiral_and_rects() {
  var spiral = window.rectangle1;
    var rectangle1 = window.rectangle2;
  return {
    "ss": deserialize_clipper_poly(spiral),
    "cc": deserialize_clipper_poly(rectangle1)
  };
}

function get_rounded_grid_and_star() {
  var rounded_grid = window.rectangle1;
  var star = window.rectangle2;   
  return {
    "ss": deserialize_clipper_poly(rounded_grid),
    "cc": deserialize_clipper_poly(star)
  };
}

function get_glyph_and_grid() {
  var glyph = window.rectangle1;
  var grid = window.rectangle2;
  return {
    "ss": deserialize_clipper_poly(glyph),
    "cc": deserialize_clipper_poly(grid)
  };
}

function get_lines_and_star_and_donut() {
  var lines = window.rectangle1;
  var star_and_donut = "[]";
  var star_and_donut2 = window.rectangle1;
  return {
    "ss": deserialize_clipper_poly(lines),
    "cc": deserialize_clipper_poly(star_and_donut)
  };
}

function get_collinear_test() {
  //console.log("collinear");
  var collinear = window.rectangle1;
  return {
    "ss": deserialize_clipper_poly(collinear),
    "cc": []
  };
}

function get_spiral_strips() {
  var spiral = window.rectangle1;
  var strips = window.rectangle2;
  return {
    "ss": deserialize_clipper_poly(spiral),
    "cc": deserialize_clipper_poly(strips)
  };
}

function get_custom_poly() {
  var selected_value = $("#custom_polygons_select").val();
  var arr = $.totalStorage('custom_polygons');
  if (selected_value !== "") {
    //selected_value = parseInt(selected_value,10);
    return {
      "ss": deserialize_clipper_poly(arr[selected_value].subj),
      "cc": deserialize_clipper_poly(arr[selected_value].clip)
    };
  }
  //else return { "ss": '[[{"X":0,"Y":0},{"X":1,"Y":1}]]', "cc": '[[{"X":0,"Y":0},{"X":1,"Y":1}]]' };
  else return {
    "ss": deserialize_clipper_poly(default_custom_subject_polygon),
    "cc": deserialize_clipper_poly(default_custom_clip_polygon)
  };
}

function get_random_polys(which, polygon) {
  var point_count, polygon_count;
  if (which == "subj") {
    point_count = rnd_sett.subj_point_count;
    polygon_count = rnd_sett.subj_polygon_count;
  }
  else if (which == "clip") {
    point_count = rnd_sett.clip_point_count;
    polygon_count = rnd_sett.clip_polygon_count;
  }
  if (arguments.length === 1) polygon = parseInt($('input[type="radio"][name="polygons"]:checked').val(), 10);
  if (polygon != 4 && polygon != 5) return new ClipperLib.Paths();
  var svg = $("#p");
  var margin = 10;
  rnd_sett.rand_min_x = 0 + margin;
  rnd_sett.rand_max_x = parseFloat(svg.attr("width"), 10) - margin;
  rnd_sett.rand_min_y = 0 + margin;
  rnd_sett.rand_max_y = parseFloat(svg.attr("height"), 10) - margin;
  var i, j, pp, np = new ClipperLib.Paths(),
    prev_x = null,
    prev_y = null,
    horiz_or_vertic = null,
    prev_horiz_or_vertic = null;
  for (i = 0; i < polygon_count; i++) {
    np[i] = new ClipperLib.Path();
    for (j = 0; j < point_count; j++) {
      pp = new ClipperLib.FPoint();
      if (polygon == 4) {
        horiz_or_vertic = rnd("int", 0, 1); // 0 = horiz, 1 = vertic
        if (prev_horiz_or_vertic === horiz_or_vertic) {
          if (horiz_or_vertic === 0) horiz_or_vertic = 1;
          else if (horiz_or_vertic === 1) horiz_or_vertic = 0;
          else horiz_or_vertic = 0;
        }
        if (horiz_or_vertic === 0) // horiz => y remains same
        {
          pp.X = round(rnd("float", rnd_sett.rand_min_x, rnd_sett.rand_max_x));
          if (prev_y == null) pp.Y = round(rnd("float", rnd_sett.rand_min_y, rnd_sett.rand_max_y));
          else pp.Y = prev_y;
          prev_x = pp.X;
          prev_y = pp.Y;
          prev_horiz_or_vertic = horiz_or_vertic;
        }
        else // vertic => x remains same
        {
          pp.Y = round(rnd("float", rnd_sett.rand_min_y, rnd_sett.rand_max_y));
          if (prev_x == null) pp.X = round(rnd("float", rnd_sett.rand_min_x, rnd_sett.rand_max_x));
          else pp.X = prev_x;
          prev_x = pp.X;
          prev_y = pp.Y;
          prev_horiz_or_vertic = horiz_or_vertic;
        }
        // last point fix
        if (j == point_count - 1 && point_count !== 1) {
          if (horiz_or_vertic === 0) // horiz => y remains same
          {
            pp.X = np[i][0].X;
          }
          else // vertic => x remains same
          {
            pp.Y = np[i][0].Y;
          }
          np[i].push(pp);
        }
        else np[i].push(pp);
      }
      else if (polygon == 5) {
        pp.X = round(rnd("float", rnd_sett.rand_min_x, rnd_sett.rand_max_x));
        pp.Y = round(rnd("float", rnd_sett.rand_min_y, rnd_sett.rand_max_y));
        np[i].push(pp);
      }
    }
    prev_x = null;
    prev_y = null;
    horiz_or_vertic = null;
    prev_horiz_or_vertic = null;
  }
  rnd_sett.scale = scale;
  return np;
}

function scale_again_random_poly(poly) {
  var i, j;
  for (i = 0; i < poly.length; i++) {
    for (j = 0; j < poly[i].length; j++) {
      poly[i][j].X = round(poly[i][j].X / rnd_sett.scale);
      poly[i][j].Y = round(poly[i][j].Y / rnd_sett.scale);
    }
  }
  return poly;
}

function scale_again_random_grid_poly(poly) {
  var i, j;
  for (i = 0; i < poly.length; i++) {
    for (j = 0; j < poly[i].length; j++) {
      poly[i][j].X = round(poly[i][j].X / rnd_grid_sett.scale);
      poly[i][j].Y = round(poly[i][j].Y / rnd_grid_sett.scale);
    }
  }
}

function rnd(intfloat, Amin, Amax) {
  var num;
  if (intfloat == "float") num = (Amin + (Amax - Amin) * Math.random()).toFixed(2);
  else if (intfloat == "int") num = Math.floor(Amin + (1 + Amax - Amin) * Math.random());
  return num;
}

function Odd(i) {
  return (i % 2 != 0);
}

function rand(from, to) {
  return Math.floor(Math.random() * (to - from + 1) + from);
}

function del(subj) {
  //randomly delete roughly 1/8 of them ...
  for (var i = 0; i < 100; i++)
    delete subj[rand(0, 800)];
  var subj2 = [];
  for (var i = 0; i < 800; i++) {
    if (subj[i] != null) subj2.push(subj[i]);
  }
  return subj2;
}

function get_random_grids(subj_or_clip, grid_type) {
  gsize = 20 * scale;
  var hor_amount = 21 - 1;
  var ver_amount = 17 - 1;
  hor_amount = 12 - 1;
  ver_amount = 12 - 1;
  var amount = hor_amount * ver_amount * 2;
  var hor_offset = ((500 - (gsize / scale * hor_amount)) * scale) / 2;
  var ver_offset = ((350 - (gsize / scale * ver_amount)) * scale) / 2;
  if (1 == 1) {
    if (grid_type == 1) {
      //make a grid of triangles ...
      var subj = new Array(amount);
      for (var i = 0; i < ver_amount; i++)
        for (var j = 0; j < hor_amount; j++) {
          subj[i * 40 + j * 2] = [
            new ClipperLib.FPoint(j * gsize + hor_offset, i * gsize + ver_offset),
            new ClipperLib.FPoint(j * gsize + gsize + hor_offset, i * gsize + ver_offset),
            new ClipperLib.FPoint(j * gsize + hor_offset, i * gsize + gsize + ver_offset)
          ];
          subj[i * 40 + j * 2 + 1] = [
            new ClipperLib.FPoint(j * gsize + gsize + hor_offset, i * gsize + ver_offset),
            new ClipperLib.FPoint(j * gsize + gsize + hor_offset, i * gsize + gsize + ver_offset),
            new ClipperLib.FPoint(j * gsize + hor_offset, i * gsize + gsize + ver_offset)
          ];
        }
        //randomly delete roughly 1/8 of them ...
      subj = del(subj);
    }
    else if (grid_type == 2) {
      //alternative shapes 1 ...
      var subj = new Array(amount / 2);
      for (var i = 0; i < ver_amount; i++)
        for (var j = 0; j < hor_amount; j++) {
          if (Odd(i)) var k = 4;
          else k = -4;
          subj[i * 20 + j] = [
            new ClipperLib.FPoint(j * gsize + k + hor_offset, i * gsize + ver_offset),
            new ClipperLib.FPoint(j * gsize + gsize + k + hor_offset, i * gsize + ver_offset),
            new ClipperLib.FPoint(j * gsize + gsize + k + hor_offset, i * gsize + gsize + ver_offset),
            new ClipperLib.FPoint(j * gsize + k + hor_offset, i * gsize + gsize + ver_offset)
          ];
        }
        //randomly delete roughly 1/8 of them ...
      subj = del(subj);
    }
    else {
      //alternative shapes 2 ...
      var subj = new Array(amount / 2);
      for (var i = 0; i < ver_amount; i++)
        for (var j = 0; j < hor_amount; j++) {
          subj[i * 20 + j] = [
            new ClipperLib.FPoint(j * gsize + gsize / 2 + hor_offset, i * gsize + ver_offset),
            new ClipperLib.FPoint(j * gsize + gsize + gsize / 2 + hor_offset, i * gsize + ver_offset),
            new ClipperLib.FPoint(j * gsize + gsize - gsize / 2 + hor_offset, i * gsize + gsize + ver_offset),
            new ClipperLib.FPoint(j * gsize - gsize / 2 + hor_offset, i * gsize + gsize + ver_offset)
          ];
        }
        //randomly delete roughly 1/8 of them ...
      subj = del(subj);
    }
    //random_grid_subj = subj;
  }
  else subj = random_grid_subj;
  rnd_grid_sett.scale = scale;
  return subj;
}

function get_rect_poly() {
  var rect_poly = '[[{"X":100,"Y":100},{"X":200,"Y":100},{"X":200,"Y":200},{"X":100,"Y":200},{"X":100,"Y":100}]]';
  rect_poly = '[[{"X":100,"Y":100},{"X":100,"Y":100},{"X":200,"Y":100},{"X":200,"Y":200},{"X":100,"Y":200},{"X":100,"Y":100}]]';
  return deserialize_clipper_poly(rect_poly);
}

function round_old(a) {
  if (global_do_not_round_and_scale) return a;
  else
    return Math.floor(a * scale);
}
function round(a) {
  if (global_do_not_round_and_scale) return a;
  else
    return a * scale;
}

window.lsk = 0;

function deserialize_clipper_poly(polystr) {
  window.lsk++;
  var poly = JSON.parse(polystr);
  var i, j, pp, n = [
      []
    ],
    m, pm;
  var np = new ClipperLib.Paths();
  for (i = 0, m = poly.length; i < m; i++) {
    np[i] = new ClipperLib.Path();
    for (j = 0, pm = poly[i].length; j < pm; j++) {
      pp = new ClipperLib.FPoint();
      if (!isNaN(Number(poly[i][j].X)) && !isNaN(Number(poly[i][j].Y))) {
        pp.X = round(Number(poly[i][j].X));
        pp.Y = round(Number(poly[i][j].Y));
        if (benchmark_running) {
          if (pp.X > bench.max_point_x) bench.max_point_x = pp.X;
          if (pp.Y > bench.max_point_y) bench.max_point_y = pp.Y;
          if (pp.X < bench.min_point_x) bench.min_point_x = pp.X;
          if (pp.Y < bench.min_point_y) bench.min_point_y = pp.Y;
          if (typeof (bench.points["L" + pp.X]) == "undefined") bench.points["L" + pp.X] = scale + ":" + pp.X + ":" + poly[i][j].X;
          if (typeof (bench.points["L" + pp.Y]) == "undefined") bench.points["L" + pp.Y] = scale + ":" + pp.Y + ":" + poly[i][j].Y;
        }
        np[i].push(pp);
      }
      else return n;
    }
  }
  return np;
}
(function () {
  "use strict";
  SVG.create = function () {
    p = Raphael("svgcontainer", 500, 350);
    p.canvas.setAttribute("id", "p");
    var str = "<filter id='innerbewel' x0='-50%' y0='-50%' width='200%' height='200%' >";
    str += "<feGaussianBlur in='SourceAlpha' stdDeviation='2' result='blur'/>";
    str += "<feOffset dy='3' dx='3'/>";
    str += "<feComposite in2='SourceAlpha' operator='arithmetic'";
    str += " k2='-1' k3='1' result='hlDiff'/>";
    str += "<feFlood flood-color='white' flood-opacity='0.8'/>"; // changed to 1.0 for speed
    str += "<feComposite in2='hlDiff' operator='in'/>";
    str += "<feComposite in2='SourceGraphic' operator='over' result='withGlow'/>";
    str += "<feOffset in='blur' dy='-3' dx='-3'/>";
    str += "<feComposite in2='SourceAlpha' operator='arithmetic'";
    str += " k2='-1' k3='1' result='shadowDiff'/>";
    str += "<feFlood flood-color='black' flood-opacity='0.5'/>"; // changed to 1.0 for speed
    str += "<feComposite in2='shadowDiff' operator='in'/>";
    str += "<feComposite in2='withGlow' operator='over'/>";
    str += "</filter>";
    var markers = '';
    // Markers to show start, mid and end points of path
    markers += '<marker id="StartMarker" viewBox="0 0 10 10" refX="0" refY="5" markerUnits="strokeWidth" markerWidth="10" markerHeight="10" stroke="red" stroke-width="1" fill="none" orient="auto">';
    markers += '<path d="M0,5L10,5M5,0L10,5M5,10L10,5">';
    markers += '</marker>';
    markers += '<marker id="MidMarker" stroke-opacity="1.0" viewBox="-1 -1 12 12" refX="5" refY="5" markerWidth="4" markerHeight="4" stroke="red" stroke-width="1" fill="yellow" stroke-opacity="0.5" fill-opacity="0.5" orient="0" markerUnits="strokeWidth">';
    markers += '<circle cx="5" cy="5" r="5"></circle>';
    markers += '</marker>';
    markers += '<marker id="EndMarker" viewBox="0 0 10 10" refX="5" refY="5" markerUnits="strokeWidth" markerWidth="8" markerHeight="8" stroke="blue" stroke-width="1" fill="none" orient="auto">';
    markers += '<rect x="0" y="0" width="10" height="10"></rect>';
    markers += '</marker>';
    // This second invisible marker is needed to avoid noisy after images in Chrome:
    markers += '<marker id="StartMarker2" viewBox="0 0 10 10" refX="0" refY="5" markerUnits="strokeWidth" markerWidth="10" markerHeight="10" stroke="none" fill="none" orient="auto">';
    markers += '<path d="M0,5L10,5M5,0L10,5M5,10L10,5">';
    markers += '</marker>';
    markers += '<marker id="MidMarker2" stroke-opacity="1.0" viewBox="-1 -1 12 12" refX="5" refY="5" markerWidth="4" markerHeight="4" stroke="none" fill="none" orient="0" markerUnits="strokeWidth">';
    markers += '<circle cx="5" cy="5" r="5"></circle>';
    markers += '</marker>';
    markers += '<marker id="EndMarker2" viewBox="0 0 10 10" refX="5" refY="5" markerUnits="strokeWidth" markerWidth="8" markerHeight="8" stroke="none" fill="none" orient="auto">';
    markers += '<rect x="0" y="0" width="10" height="10"></rect>';
    markers += '</marker>';
    $("body").append("<svg id='dummy' style='display:none'><defs>" + str + markers + "</defs></svg>");
    $("#p defs").append($("#innerbewel"));
    $("#p defs").append($("#dummy marker"));
    $("#dummy").remove();
    return p;
  };
  SVG.addpaths = function (a, b, c, a1, b1) {
    if (a) {
      subj_subpolygons = a.length;
      a = this.polys2path(a, "1");
      if (sub_poly_links_update) {
        if (typeof (subj_subpolygons) == "undefined") subj_subpolygons = 0;
        $("#subj_subpolygons").html(subj_subpolygons);
        $("#subj_points_in_subpolygons").html(this.sub_poly_links);
        $("#subj_points_total").html(this.total.toString());
        subj_points_total = this.total;
      }
    }
    if (b) {
      clip_subpolygons = b.length;
      b = this.polys2path(b, "2");
      if (sub_poly_links_update) {
        if (typeof (clip_subpolygons) == "undefined") clip_subpolygons = 0;
        $("#clip_subpolygons").html(clip_subpolygons);
        $("#clip_points_in_subpolygons").html(this.sub_poly_links);
        $("#clip_points_total").html(this.total.toString());
        clip_points_total = this.total;
      }
    }
    if (typeof (c) != "undefined" && typeof (c.length) != "undefined") solution_subpolygons = c.length;
    else solution_subpolygons = 0;
    if (c) {
      c = this.polys2path(c, "3");
      if (sub_poly_links_update) {
        $("#solution_subpolygons").html(solution_subpolygons);
        $("#solution_points_in_subpolygons").html(this.sub_poly_links);
        $("#solution_points_total").html(this.total.toString());
        solution_points_total = this.total;
      }
    }
    if (sub_poly_links_update) {
      $("#points_total").html((subj_points_total + clip_points_total + solution_points_total).toString());
      if (isNaN(subj_subpolygons)) subj_subpolygons = 0;
      else if (isNaN(clip_subpolygons)) clip_subpolygons = 0;
      else if (isNaN(solution_subpolygons)) solution_subpolygons = 0;
      $("#all_subpolygons").html(subj_subpolygons + clip_subpolygons + solution_subpolygons);
    }
    if (a) p1 = p.path(a);
    if (b) p2 = p.path(b);
    if (c) p3 = p.path(c);
    if (a) p1.node.setAttribute("id", "p1");
    if (b) p2.node.setAttribute("id", "p2");
    if (c) p3.node.setAttribute("id", "p3");
    if (c && bevel) $("#p3").attr("filter", "url(#innerbewel)");
    else if (c && !bevel) $("#p3").removeAttr("filter");
    if (!subj_is_closed && a) p1.node.setAttribute("class", "openpath");
    else if (subj_is_closed && a) $("#p1").removeAttr("class");
    if (a) $("#p1").removeAttr("fill stroke");
    if (b) $("#p2").removeAttr("fill stroke");
    if (c) $("#p3").removeAttr("fill stroke");
    var PolyFillType = {
      pftEvenOdd: 0,
      pftNonZero: 1,
      pftPositive: 2,
      pftNegative: 3
    };
    if (a) {
      if (a1 == PolyFillType.pftEvenOdd) $("#p1").attr("fill-rule", "evenodd");
      else $("#p1").attr("fill-rule", "nonzero");
    }
    if (b) {
      if (b1 == PolyFillType.pftEvenOdd) $("#p2").attr("fill-rule", "evenodd");
      else $("#p2").attr("fill-rule", "nonzero");
    }
    //  p.setViewBox(bbox.x, bbox.y, bbox.width, bbox.height, true);
    //  p.setSize(bbox.width, bbox.height);
  };
  SVG.sub_poly_counts = null;
  SVG.sub_poly_links = null;
  SVG.total = null;
  SVG.polys2path = function (a, fr) {
    scaled_paths[fr] = [];
    var path = "",
      i, j, link, d;
    this.sub_poly_counts = [];
    this.sub_poly_links = "";
    this.total = 0;
    if (!scale) scale = 1;
    var a_length = a.length;
    var a_i_length;
    var classi = "subpolylinks";
    for (i = 0; i < a_length; i++) {
      this.sub_poly_counts.push(a[i].length);
      d = "";
      a_i_length = a[i].length;
      if (a_i_length) {
        for (j = 0; j < a_i_length; j++) {
          this.total++;
          if (j == 0) {
            d += "M";
          }
          else {
            d += "L";
          }
          d += (a[i][j].X / scale) + ", " + (a[i][j].Y / scale);
        }
        if (!(!subj_is_closed && fr == 1)) d += "Z"; //Z only for closed subj paths
      }
      path += d;
      if (sub_poly_links_update) {
        if (d.trim() == "Z") d = "";
        scaled_paths[fr].push(d);
        if (benchmark_running) classi = "subpolylinks_disabled";
        link = '<span class="' + classi + '" onclick="popup_path(' + i + ',' + fr + ',' + a_i_length + ')" onmouseover="show_path(' + i + ',' + fr + ',' + a_i_length + ')" onmouseout="hide_path()" >' + a_i_length + '</span>, ';
        this.sub_poly_links += link;
      }
    }
    if (sub_poly_links_update) this.sub_poly_links = this.sub_poly_links.substring(0, this.sub_poly_links.length - 2);
    if (path.trim() == "Z") path = "";
    if (benchmark_running) $(".subpolylinks").removeClass("subpolylinks subpolylinks_disabled").addClass("subpolylinks_disabled");
    else $(".subpolylinks_disabled").removeClass("subpolylinks subpolylinks_disabled").addClass("subpolylinks");
    return path;
  }
  window.SVG = SVG;
})()

function toint(a) {
  a = parseInt(a, 10);
  return a;
}
// Englarges mypath ( = black partially transparent path)
// when clicked
function popup_path(i, fr, point_count) {
  if (point_count == 0) return;
  if (benchmark_running) return false;
  if (typeof (i) == "undefined") d = scaled_paths[fr].join(" ");
  else d = scaled_paths[fr][i];
  var points_string = normalize_clipper_poly(d, true); // quiet
  var area;
  SolutionPolygonClicked = false;
  SolutionPolygonString = "";
  if (points_string !== false) {
    if (typeof (i) == "undefined") // which means that "Points" column is clicked
    {
      if (fr == 3) {
        $("#polygon_explorer_string_inp").val(format_output(points_string, "ExPolygons"));
        SolutionPolygonClicked = true;
        SolutionPolygonString = points_string;
      }
      else {
        $("#polygon_explorer_string_inp").val(format_output(points_string));
      }
      var scaled_paths_length = scaled_paths.length;
      var points_str;
      area = 0;
      for (var j = 0; j < scaled_paths_length; j++) {
        points_str = normalize_clipper_poly(scaled_paths[fr][j], true);
        if (points_str !== false) {
          var polygon = JSON.parse(points_str.replace(/^\[\[/, "[").replace(/\]\]$/, "]"));
          area += ClipperLib.Clipper.Area(polygon);
        }
      }
      var points_scaled = JSON.parse(points_string);
      var polygonal_perimeter = ClipperLib.JS.PerimeterOfPaths(points_scaled, true, 1);
      var line_perimeter = ClipperLib.JS.PerimeterOfPaths(points_scaled, false, 1);
      var bounds = ClipperLib.JS.BoundsOfPaths(points_scaled, 1);
      $("#area").html("Area: " + area + "<br>Polygonal perimeter: " + polygonal_perimeter + "<br>Line perimeter: " + line_perimeter + "<br>Height: " + (bounds.bottom - bounds.top) + "<br>Width: " + (bounds.right - bounds.left));
    }
    else {
      $("#polygon_explorer_string_inp").val(format_output(points_string).replace(/^\[\[/, "[").replace(/\]\]$/, "]"));
      area = ClipperLib.Clipper.Area(JSON.parse(points_string.replace(/^\[\[/, "[").replace(/\]\]$/, "]")));
      var points_scaled = JSON.parse(points_string);
      var polygonal_perimeter = ClipperLib.JS.PerimeterOfPaths(points_scaled, true, 1);
      var line_perimeter = ClipperLib.JS.PerimeterOfPaths(points_scaled, false, 1);
      var bounds = ClipperLib.JS.BoundsOfPaths(points_scaled, 1);
      $("#area").html("Area: " + area + "<br>Polygonal perimeter: " + polygonal_perimeter + "<br>Line perimeter: " + line_perimeter + "<br>Height: " + (bounds.bottom - bounds.top) + "<br>Width: " + (bounds.right - bounds.left));
    }
  }
  else {
    $("#polygon_explorer_string_inp").val("Some error occurred when parsing polygon points!");
  }
  $(mypath.node).removeAttr('fill stroke').attr('class', 'svg_mypath');
  $(mypath.node).attr('fill-rule', $('#p' + fr).attr('fill-rule'));
  $(mypath.node).attr('vector-effect', 'non-scaling-stroke'); // This is not supported in IE 9 and IE10 pre!
  var bb = mypath.node.getBBox();
  var svg_w = toint($('#p').attr('width'));
  var svg_h = toint($('#p').attr('height'));
  var x_scale = (svg_w - 20) / bb.width;
  var y_scale = (svg_h - 20) / bb.height;
  var scal = Math.min(x_scale, y_scale);
  var x_trans = -(bb.x + bb.width / 2) + svg_w / 2;
  var y_trans = -(bb.y + bb.height / 2) + svg_h / 2;
  $('#StartMarker')[0].setAttribute('markerWidth', 10 / scal * 2);
  $('#StartMarker')[0].setAttribute('markerHeight', 10 / scal * 2);
  $('#MidMarker')[0].setAttribute('markerWidth', 4 / scal * 2);
  $('#MidMarker')[0].setAttribute('markerHeight', 4 / scal * 2);
  $('#EndMarker')[0].setAttribute('markerWidth', 4 / scal * 2);
  $('#EndMarker')[0].setAttribute('markerHeight', 4 / scal * 2);
  mypath.animate({
    'transform': 't' + x_trans + ' ' + y_trans + 's' + scal + ' ' + scal
  }, 500, function () {
    $(mypath.node).attr({
      'marker-start': 'url(#StartMarker)',
      'marker-mid': 'url(#MidMarker)',
      'marker-end': 'url(#EndMarker)'
    });
  });
}
// Shows mypath ( = black partially transparent path)
// when hovered
function show_path(i, fr, point_count) {
  if (point_count == 0) return;
  if (benchmark_running) return false;
  var d;
  if (typeof (i) == "undefined")
    d = scaled_paths[fr].join(" ");
  else
    d = scaled_paths[fr][i];
  mypath = p.path(d);
  $(mypath.node).removeAttr('fill stroke').attr('class', 'svg_mypath');
  $(mypath.node).attr('fill-rule', $('#p' + fr).attr('fill-rule'));
  $(mypath.node).attr('vector-effect', 'non-scaling-stroke');
}
// Hides mypath ( = black partially transparent path)
function hide_path() {
  if (mypath == null) return;
  $(mypath.node).attr({
    'marker-start': 'url(#StartMarker2)',
    'marker-mid': 'url(#MidMarker2)',
    'marker-end': 'url(#EndMarker2)'
  });
  if ($(mypath.node).attr('transform')) mypath.animate({
    'transform': 's1 1'
  }, 500, function () {
    this.animate({
      'opacity': '0'
    }, 500, function () {
      this.remove();
    });
  });
  else mypath.animate({
    'opacity': '0'
  }, 300, function () {
    this.remove();
  });
}

function set_default_custom_polygon() {
  var subj = default_custom_subject_polygon;
  var clip = default_custom_clip_polygon;
  var def_obj = {
    "subj": subj,
    "clip": clip
  };
  var arr = $.totalStorage('custom_polygons');
  if (typeof (arr) == "undefined" || arr === null || !isArray(arr) || arr.length == 0) arr = [];
  arr[0] = def_obj;
  $.totalStorage('custom_polygons', arr);
}

function update_custom_polygons_select() {
  var arr = $.totalStorage('custom_polygons');
  var selected_value = $("#custom_polygons_select").val();
  if (!selected_value && selected_value + "" !== "0") selected_value = 0;
  $("#custom_polygons_select option").remove();
  var arr_length = 0;
  if (isArray(arr)) arr_length = arr.length;
  var selected_txt, i;
  if (arr_length > 0)
    for (i = 0; i < arr_length; i++) {
      selected_txt = "";
      if (i == selected_value) selected_txt = "selected";
      if (arr[i] !== null) $("#custom_polygons_select").append('<option ' + selected_txt + ' value="' + i + '">Poly ' + i + '</option>');
    }
  else set_default_custom_polygon();
  if ($("#custom_polygons_select option").length === 0) set_default_custom_polygon();
  // If previously selected value is removed, select the next one
  if (arr_length > 0)
    if (arr[selected_value] === null) {
      for (i = parseInt(selected_value, 10); i < arr_length; i++) {
        if (arr[i] !== null) {
          $('#custom_polygons_select').val(i);
          break;
        }
      }
    }
  $('#custom_polygons_select').change();
}

function show_alert(e, obj, txt) {
  var x = e.pageX - obj.offsetLeft + $(obj).width() / 2;
  var y = e.pageY - obj.offsetTop;
  $("#custom_poly_message_box_green")
    .stop()
    .css({
      height: '0px',
      opacity: '0',
      left: x + 1,
      top: y - 1,
      width: '0px',
      display: 'block'
    })
    .html("")
    .animate({
      left: (x + 200),
      top: (y - 40),
      height: '40px',
      width: '200px',
      opacity: '0.9'
    }, 300, function () {
      $(this).html(txt).animate({
        opacity: '0.9'
      }, 1200, function () {
        $(this).animate({
          opacity: 0
        }, 600, function () {
          $(this).css("display", "none");
        })
      })
    });
}

function update_fieldset_heights() {
  $("#misc_fieldset").css("min-height", "").css("height", "");
  $("#polygon_explorer_fieldset").css("min-height", "").css("height", "");
  $("#benchmark_fieldset_f").css("min-height", "").css("height", "");
  var td_heights = [];
  td_heights.push($("#td0").innerHeight());
  td_heights.push($("#td1").innerHeight());
  td_heights.push($("#td2").innerHeight());
  td_heights.push($("#td3").innerHeight());
  var max_td_height = Array_max(td_heights);
  var max_index;
  var children_heights;
  var children_heights_arr = [];
  for (var i = 0; i < td_heights.length; i++) {
    children_heights = 0;
    if (td_heights != max_td_height || 1 == 1) {
      $("#td" + i).find("fieldset, #svgcontainer, #filltype_cliptype_table").not("[id='']").each(function () {
        if ($(this).css("display") != "none") {
          children_heights += $(this).outerHeight(true);
          //console.log(this.type + ":" + $(this).attr("id") + ":" + $(this).outerHeight(true) + ":" + $(this).outerWidth(true));
        }
        else {
          //console.log("----- NOT COUNTED:"+this.type + ":" + $(this).attr("id") + ":" + $(this).outerHeight(true) + ":" + $(this).outerWidth(true));
        }
      });
    }
    children_heights_arr.push(children_heights);
    //console.log("-------------");
  }
  var misc_fieldset_height = $("#misc_fieldset").height();
  var polygon_explorer_fieldset_height = $("#polygon_explorer_fieldset").height();
  var benchmark_fieldset_f_height = $("#benchmark_fieldset_f").height();
  misc_fieldset_height += (max_td_height - children_heights_arr[0]) +
    $("#misc_fieldset").outerHeight(true);
  polygon_explorer_fieldset_height += (max_td_height - children_heights_arr[1]);
  benchmark_fieldset_f_height += (max_td_height - children_heights_arr[2]);
  $("#misc_fieldset").css("min-height", misc_fieldset_height + "px");
  $("#polygon_explorer_fieldset").css("min-height", polygon_explorer_fieldset_height + "px");
  $("#benchmark_fieldset_f").css("min-height", benchmark_fieldset_f_height + "px");
}

function resize() {
  myresize();
}

function myresize() {
  var freeheight = $(window).height();
  var polygon_explorer_div_max_height = freeheight - 360;
  if (polygon_explorer_div_max_height < 170) polygon_explorer_div_max_height = 170;
  // This causes problems when resizing polygon explorer textarea, so have to comment:
  //$("#polygon_explorer_div").css("max-height", polygon_explorer_div_max_height +"px");
}
// ADDITIONAL SVG WINDOW STARTS
window.update_enlarged_SVG = false;
window.update_enlarged_SVG_source = false;
window.svg_source_place = "svg_source_container";

function svg_source_enlarge() {
  var source = $("#svg_source_textarea").val().replace(/ id=\"/g, ' id="_');
  $("#enlarged_svg").html(source);
  var original_height = $("#_p").attr("height");
  var original_width = $("#_p").attr("width");
  // get bbox of all children of svg
  $("body").append('<div id="dummy" style="display:block;visibility:hidden"><svg><g id="g123"></g></svg></div>');
  $("#g123").append($("#_p").children().clone());
  $("#dummy").html($("#dummy").html());
  var bb = $("#g123")[0].getBBox();
  var g_width = bb.width + 10;
  var g_height = bb.height + 10;
  var g_x = bb.x - 5;
  var g_y = bb.y - 5;
  $("#dummy").remove();
  /*
        $("body").append('<img id="width_img" width="100%" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7">');
        var window_width = parseInt(window.getComputedStyle($("#width_img")[0],null).getPropertyValue("width"));
        $("#width_img").remove();
        */
  $("#_p").attr("viewBox", g_x + " " + g_y + " " + g_width + " " + g_height);
  $("#enlarged_svg").html($("#enlarged_svg").html());
  $("#enlarged_svg").css("display", "block");
  if (svg_source_place == "svg_source_container") // at the bottom
  {
    $("#_p").attr("width", window_width);
    $("#_p").attr("height", parseInt((window_width / original_width) * original_height));
    $("#_p1,#_p2,#_p3").css("stroke-width", 0.8 * (original_width / window_width));
  }
  else // at the right
  {
    $("#_p").attr("height", window_height);
    $("#_p").attr("width", parseInt((window_height / original_height) * original_width));
    $("#_p1,#_p2,#_p3").css("stroke-width", 0.8 * (original_height / window_height));
  }
  $("#svg_source_textarea").css("display", "none");
  if (benchmark_running) var disabled = "disabled";
  else disabled = "";
  $("#svg_source_enlarge_button").html('<button ' + disabled + ' class="textarea_hide_buttons" onClick="show_svg_source_f()" title="Show SVG source">Show SVG source</button>');
  update_enlarged_SVG_source = true;
  update_enlarged_SVG = true;
}

function show_svg_source_f() {
  $("#svg_source_textarea").css("display", "block");
  show_svg_source_click("non_click");
  $("#enlarged_svg").html("");
  if (benchmark_running) var disabled = "disabled";
  else disabled = "";
  $("#svg_source_enlarge_button").html('<button ' + disabled + ' class="textarea_hide_buttons" onClick="svg_source_enlarge()" title="Show SVG">Show SVG</button>');
  update_enlarged_SVG_source = true;
  update_enlarged_SVG = false;
}
window_height = $(document).height() * 0.9;
window_width = $(document).width() * 0.9;

function show_svg_source_click(non_click) {
  update_enlarged_SVG_source = true;
  if (!update_enlarged_SVG) update_enlarged_SVG = false;
  if (non_click !== "non_click") {
    if ($("#show_svg_source").html() == "Show SVG, Bottom") {
      $("#show_svg_source").html("Show SVG, Right");
      $("#show_svg_source").attr("title", "Show current SVG and it's source at the right side of the page");
      svg_source_place = "svg_source_container";
      $("#svg_source_container2").html("");
    }
    else {
      $("#show_svg_source").html("Show SVG, Bottom");
      $("#show_svg_source").attr("title", "Show current SVG and it's source at the bottom of the page");
      svg_source_place = "svg_source_container2";
      $("#svg_source_container").html("");
    }
    var textarea_str = '<div id="svg_source_textarea_div">';
    textarea_str += '<button class="textarea_hide_buttons" onClick="$(\'#svg_source_textarea_div\').remove();update_enlarged_SVG_source=false;update_enlarged_SVG=false" title="Hide SVG source">Hide</button>';
    if (benchmark_running) var disabled = "disabled";
    else disabled = "";
    textarea_str += '<span id="svg_source_enlarge_button"><button ' + disabled + ' class="textarea_hide_buttons" onClick="svg_source_enlarge()" title="Show SVG">Show SVG</button></span><br>';
    textarea_str += '<div style="display:none" id="enlarged_svg"></div>';
    textarea_str += '<textarea id="svg_source_textarea"></textarea>';
    textarea_str += '</div>';
    $("#" + svg_source_place).append(textarea_str);
  }
  var svg_source = $("#svgcontainer").html().replace(/\>/g, ">\n");
  $("#svg_source_textarea").val(svg_source);
  //svg_source_enlarge();
}
// ADDITIONAL SVG WINDOW ENDS
// BENCHMARKING STARTS  
function benchmark2(i) {
  var start_time = new Date().getTime();
  var obj = bench_glob[i];
  $("#clean").removeAttr('checked');
  clean = false;
  $("#lighten").removeAttr('checked');
  lighten = false;
  joinType = obj.joinType;
  $('input[type="radio"][name="joinType"][value="' + joinType + '"]').attr('checked', 'checked');
  endType = obj.endType;
  $('input[type="radio"][name="endType"][value="' + endType + '"]').attr('checked', 'checked');
  offsettable_poly = obj.offsettable_poly;
  $('input[type="radio"][name="offsettable_poly"][value="' + offsettable_poly + '"]').attr('checked', 'checked');
  delta = obj.delta;
  $('#delta').val(delta);
  miterLimit = obj.miterLimit;
  $('#miterLimit').val(miterLimit);
  arcTolerance = obj.arcTolerance;
  $('#arcTolerance').val(arcTolerance);
  AutoFix = obj.AutoFix;
  if (AutoFix) $('#AutoFix').attr('checked', 'checked');
  else $('#AutoFix').removeAttr('checked');
  ExPolygons = obj.ExPolygons;
  if (ExPolygons) $('#ExPolygons').attr('checked', 'checked');
  else $('#ExPolygons').removeAttr('checked');
  Simplify = obj.Simplify;
  if (Simplify) $('#Simplify').attr('checked', 'checked');
  else $('#Simplify').removeAttr('checked');
  PreserveCollinear = obj.PreserveCollinear;
  if (PreserveCollinear) $('#PreserveCollinear').attr('checked', 'checked');
  else $('#PreserveCollinear').removeAttr('checked');
  ReverseSolution = obj.ReverseSolution;
  if (ReverseSolution) $('#ReverseSolution').attr('checked', 'checked');
  else $('#ReverseSolution').removeAttr('checked');
  StrictlySimple = obj.StrictlySimple;
  if (StrictlySimple) $('#StrictlySimple').attr('checked', 'checked');
  else $('#StrictlySimple').removeAttr('checked');
  off_poly_closed = obj.off_poly_closed;
  if (off_poly_closed) $('#off_poly_closed').attr('checked', 'checked');
  else $('#off_poly_closed').removeAttr('checked');
  subject_fillType = obj.subject_fillType;
  $("input[name='subject_fillType'][value='" + subject_fillType + "']").attr("checked", "checked");
  clip_fillType = obj.clip_fillType;
  $("input[name='clip_fillType'][value='" + clip_fillType + "']").attr("checked", "checked");
  clipType = obj.clipType;
  $("input[name='clipType'][value='" + clipType + "']").attr("checked", "checked");
  scale = obj.scale;
  $('#scale').val(scale);
  if (obj.polygon_id == 4 || obj.polygon_id == 5) rnd_sett = obj.rnd_sett;
  $('input[type="radio"][name="polygons"][value="' + obj.polygon_id + '"]').attr('checked', 'checked').trigger("change");
  obj = null;
  window.last_completed_bench = i;
  var end_time = new Date().getTime();
  var time = end_time - start_time;
  bench_glob[i].measured_time = time;
  bench_elapsed_time += time;
  bench_glob[i].elapsed_time = bench_elapsed_time;
  // update next timeouts
  for (var lsk = i + 1; lsk < bench_glob.length; lsk++) {
    clearTimeout(bench_glob[lsk].setTimeout);
    bench_glob[lsk].setTimeout = setTimeout("benchmark2(" + (lsk) + ")",
      bench_glob[lsk].elapsed_time + lsk * bench_glob[lsk].time);
  }
  var results = '';
  var elapsed_time = end_time - bench.list[0].start;
  results += Math.floor((i + 1) / bench_glob.length * 100) + " % (";
  results += (elapsed_time / 1000).toFixed(1) + " s) of ";
  results += "benchmark " + (repeat + 1) + " / " + repeat_times + ". ";
  results += "Remaining: ";
  results += Math.floor((((elapsed_time / (i + 1)) * (bench_glob.length - i + 1)) / 1000)) + " s.";
  $("#benchmark_multiple_status").html(results);
  $("#benchmark_multiple_status").css("display", "table-cell");
  if (i == 0) {
    var multiple_runs_table = bench.print_multiple_runs();
    if ($("#benchmark_multiple_table").length) $("#benchmark_multiple_table").remove();
    $("#benchmark_multiple_table_cont").append(multiple_runs_table);
  }
  else if (i == bench_glob.length - 1) {
    if (!$("#benchmark_exports_textarea").length) {
      var textarea_str = '<div id="benchmark_exports_textarea_div">';
      textarea_str += '<button class="textarea_hide_buttons" onClick="$(\'#benchmark_exports_textarea_div\').remove()" title="Hide Benchmark exports">Hide</button><br>';
      textarea_str += '<textarea id="benchmark_exports_textarea"></textarea>';
      textarea_str += '</div>';
      $("#benchmark_exports_container").append(textarea_str);
    }
    if (benchmark_exports.indexOf("max_point_x") == -1) {
      benchmark_exports += "bench.max_point_x:" + bench.max_point_x + "\n";
      benchmark_exports += "bench.max_point_y:" + bench.max_point_y + "\n";
      benchmark_exports += "bench.min_point_x:" + bench.min_point_x + "\n";
      benchmark_exports += "bench.min_point_y:" + bench.min_point_y + "\n";
    }
    benchmark_exports += bench.totals + ";" + browserg.browser + ";" + browserg.version + "\n";
    $("#benchmark_exports_textarea").val(benchmark_exports);
    bench.totals_arr_multiple.push(bench.totals_arr[0]);
    var multiple_runs_table = bench.print_multiple_runs();
    if ($("#benchmark_multiple_table").length) $("#benchmark_multiple_table").remove();
    $("#benchmark_multiple_table_cont").append(multiple_runs_table);
    repeat++;
    if (repeat < repeat_times) {
      benchmark_automatic_click = 1;
      $("#" + clicked_benchmark_button_id).trigger("click");
    }
    else {
      bench_glob.length = 0;
      repeat = 0;
      benchmark_running = 0;
      ClipperLib.MaxSteps = ClipperLib_MaxSteps_original;
      $("#" + clicked_benchmark_button_id).html($("#" + clicked_benchmark_button_id).html().replace("Stop", "Run"));
      $("#" + clicked_benchmark_button_id).attr("title", $("#" + clicked_benchmark_button_id).attr("title").replace("Stop", "Execute"));
      $("button,input,select").removeAttr('disabled');
      $('#sub_poly_links_update').trigger("change");
    }
  }
}
(function (window) {
  var benchmark = function (varname) {
    if (typeof (varname) == "string") this.varname = varname;
    else this.varname = "";
    this.list = [];
    this.cats = [];
    this.cats.arr = [];
    this.type = "benchmark";
    this.includeSVG = true;
    this.totals = "";
    this.totals_arr = [];
    this.totals_arr_multiple = [];
    this.max_point_x = Number.NEGATIVE_INFINITY;
    this.min_point_x = Number.POSITIVE_INFINITY;
    this.max_point_y = Number.NEGATIVE_INFINITY;
    this.min_point_y = Number.POSITIVE_INFINITY;
    this.points = [];
  };
  // returns index
  // cat = category, which name belongs to
  // name = code region name or function, which is measured
  benchmark.prototype.start = function (cat, name) {
    if (cat == "") return;
    if (name == "") return;
    var b = {};
    b.start = new Date().getTime();
    b.name = name;
    b.cat = cat;
    this.list.push(b);
    return this.list.length - 1;
  }
  benchmark.prototype.end = function (index) {
    this.list[index].end = new Date().getTime();
    this.list[index].time = this.list[index].end - this.list[index].start;
    var this_list_cat = this.list[index].cat;
    var this_list_cat_counts = this_list_cat + "_counts";
    var this_list_cat_time_sum = this_list_cat + "_time_sum"
    if (typeof (this.cats[this_list_cat_counts]) == "undefined") this.cats.arr.push(this_list_cat);
    if (typeof (this.cats[this_list_cat_time_sum]) == "undefined") this.cats[this_list_cat_time_sum] = 0;
    if (typeof (this.cats[this_list_cat_counts]) == "undefined") this.cats[this_list_cat_counts] = 0;
    this.cats[this_list_cat_time_sum] += this.list[index].time;
    this.cats[this_list_cat_counts]++;
    if (typeof (bench_glob) != "undefined" && bench_glob.length > 0) {
      this.list[index].bench_glob_index = window.last_completed_bench;
    }
  }
  benchmark.prototype.clear = function () {
    this.list = [];
    this.cats = [];
    this.cats.arr = [];
    this.includeSVG = true;
    return true;
  }
  benchmark.prototype.print = function (all) {
    var tbl = '<style>';
    tbl += '.bench {width:317px;border-collapse:collapse;white-space:nowrap;}';
    tbl += '.bench td, .bench th{font-size:12px;text-align:left;border:1px solid #444444; padding:2px}';
    tbl += '.bench th{background-color:#DDDDDD;}';
    tbl += '.bench tfoot td{background-color:#DDDDDD;}';
    tbl += '.bench_foot{font-weight:bold;}';
    tbl += '</style>';
    tbl += '<table class="bench"><thead><tr>';
    tbl += '<th>Num</th>';
    tbl += '<th>Name</th>';
    tbl += '<th>Category</th>';
    tbl += '<th>Time</th>';
    tbl += '</tr></thead>';
    tbl += '<tbody>';
    var time, totaltime = 0,
      i, m;
    if (this.list && this.list.length) {
      m = this.list.length;
      // print all
      var start_index = 0;
      if (!all) {
        if (bench_glob.length) start_index = m - 3;
        else start_index = m - 16;
        if (start_index < 0) start_index = 0;
      }
      for (i = start_index; i < m; i++) {
        time = (this.list[i].time);
        tbl += '<tr><td>';
        tbl += (i + 1);
        tbl += '</td><td>';
        tbl += this.list[i].name;
        tbl += '</td><td>';
        tbl += this.list[i].cat;
        tbl += '</td><td>';
        tbl += time;
        tbl += '</td></tr>';
      }
    }
    tbl += '</tbody>';
    tbl += '</table>';
    var tbl2 = "";
    tbl2 += '<table style="margin-top:10px;margin-bottom:10px" class="bench"><thead><tr>';
    tbl2 += '<th>Num</th>';
    tbl2 += '<th>Category</th>';
    tbl2 += '<th>Calls</th>';
    tbl2 += '<th>Sum</th>';
    tbl2 += '<th>Avg</th>';
    tbl2 += '</tr></thead>';
    tbl2 += '<tbody>';
    m = this.cats.arr.length;
    var item;
    this.totals = "";
    this.totals_arr = [];
    var totals_arr_item = [];
    var counts_sum = 0,
      cat_time_sum = 0,
      cat_time_avg = 0,
      this_list_i_cat,
      this_list_i_cat_time_sum, this_list_i_cat_counts;
    if (m > 0)
      for (i = 0; i < m; i++) {
        tbl2 += '<tr><td>';
        item = (i + 1);
        tbl2 += item;
        tbl2 += '</td><td>';
        this_list_i_cat = this.cats.arr[i];
        item = this_list_i_cat;
        tbl2 += item;
        tbl2 += '</td><td>';
        this_list_i_cat_counts = this.cats[this_list_i_cat + "_counts"];
        item = this_list_i_cat_counts;
        tbl2 += item;
        counts_sum += this_list_i_cat_counts;
        tbl2 += '</td><td>';
        this_list_i_cat_time_sum = this.cats[this_list_i_cat + "_time_sum"];
        item = this_list_i_cat_time_sum;
        tbl2 += item;
        cat_time_sum += this_list_i_cat_time_sum;
        tbl2 += '</td><td>';
        item = (this_list_i_cat_time_sum / this_list_i_cat_counts).toFixed(4);
        tbl2 += item;
        tbl2 += '</td></tr>';
      }
    tbl2 += '</tbody>';
    if (m > 0) {
      tbl2 += '<tfoot><tr><td class="bench_foot" colspan="2">Total</td>';
      item = (counts_sum);
      totals_arr_item.push(item);
      this.totals += item + ";";
      tbl2 += '<td>' + item + '</td>';
      item = (cat_time_sum);
      totals_arr_item.push(item);
      this.totals += item + ";";
      tbl2 += '<td>' + item + '</td>';
      item = (cat_time_sum / counts_sum).toFixed(4);
      totals_arr_item.push(item);
      this.totals += item;
      tbl2 += '<td style="padding: 4px; background-color: hsl(125, 73%, 80%); border: 3px solid black; font-weight: bold; font-size: 16px;">' + item + '</td>';
      tbl2 += '</tr>';
      tbl2 += '</tfoot>';
    }
    tbl2 += '</table>';
    this.totals_arr.push(totals_arr_item);
    tbl += tbl2;
    if (benchmark_running) var disabled = "disabled";
    else disabled = "";
    tbl += '<button ' + disabled + ' onClick="try { ' + this.varname + '.clear();$(\'#benchmark_div\').html(' + this.varname + '.print()); return true; } catch (e) {return false}">Clear Benchmarks</button>';
    tbl += '<button ' + disabled + ' onClick="try { $(\'#benchmark_div\').html(' + this.varname + '.print(1)); } catch (e) {return false}">Show all</button>';
    return tbl;
  }
  benchmark.prototype.print_multiple_runs = function (str) {
    var tbl2 = "";
    tbl2 += '<table id="benchmark_multiple_table" style="margin-top:10px;margin-bottom:10px" class="bench"><thead><tr>';
    tbl2 += '<th>Num</th>';
    tbl2 += '<th>Calls</th>';
    tbl2 += '<th>Sum</th>';
    tbl2 += '<th>Avg</th>';
    tbl2 += '</tr></thead>';
    tbl2 += '<tbody>';
    var item, counts_sum = 0,
      time_sum = 0;
    var times_array = [];
    for (var i = 0, m = this.totals_arr_multiple.length; i < m; i++) {
      times_array.push(this.totals_arr_multiple[i][1]);
    }
    var max = Array_max(times_array);
    var min = Array_min(times_array);
    var range = max - min;
    var average = getAverageFromNumArr(times_array, 4);
    var stdev = getStandardDeviation(times_array, 4);
    var minus_range = min - average;
    var plus_range = max - average;
    for (var i = 0, m = this.totals_arr_multiple.length; i < m; i++) {
      tbl2 += '<tr><td>';
      item = (i + 1);
      tbl2 += item;
      tbl2 += '</td><td>';
      item = this.totals_arr_multiple[i][0];
      tbl2 += item;
      tbl2 += '</td><td>';
      item = this.totals_arr_multiple[i][1];
      tbl2 += item;
      time_sum += item;
      tbl2 += '</td><td>';
      item = this.totals_arr_multiple[i][2];
      tbl2 += item;
      tbl2 += '</td></tr>';
    }
    tbl2 += '<tr><td colspan="4" id="benchmark_multiple_status" style="display:none"></td></tr>';
    if (!isNaN(average)) {
      tbl2 += '<tr><td colspan="4">';
      tbl2 += '<b>Average:</b> ' + average + " ms<br>";
      tbl2 += '<b>Min:</b> ' + min + " ms<br>";
      tbl2 += '<b>Max:</b> ' + max + " ms<br>";
      tbl2 += '<b>Range:</b> ' + range.toFixed(4) + " ms<br>";
      tbl2 += '<b>Minus-Range:</b> ' + minus_range.toFixed(4) + " ms<br>";
      tbl2 += '<b>Plus-Range:</b> ' + plus_range.toFixed(4) + " ms<br>";
      tbl2 += '<b>Stdev:</b> ' + stdev + " ms<br>";
      tbl2 += '<b>Range/Average %:</b> ' + (range / average * 100).toFixed(4) + "<br>";
      tbl2 += '<b>Minus-Range/Average %:</b> ' + (minus_range / average * 100).toFixed(4) + "<br>";
      tbl2 += '<b>Plus-Range/Average %:</b> ' + (plus_range / average * 100).toFixed(4) + "<br>";
      tbl2 += '<b>Stdev/Average %:</b> ' + (stdev / average * 100).toFixed(4) + "<br>";
      tbl2 += '</td></tr>';
    }
    tbl2 += '</tbody>';
    return tbl2;
  }
  window.benchmark = benchmark;
})(window);
// Programmer: Larry Battle 
// Date: Mar 06, 2011
// Purpose: Calculate standard deviation, variance, and average among an array of numbers.
function isArray(obj) {
  return Object.prototype.toString.call(obj) === "[object Array]";
};

function getNumWithSetDec(num, numOfDec) {
  var pow10s = Math.pow(10, numOfDec || 0);
  return (numOfDec) ? Math.round(pow10s * num) / pow10s : num;
};

function getAverageFromNumArr(numArr, numOfDec) {
  if (!isArray(numArr)) {
    return false;
  }
  var i = numArr.length,
    sum = 0;
  while (i--) {
    sum += numArr[i];
  }
  return getNumWithSetDec((sum / numArr.length), numOfDec);
};

function getVariance(numArr, numOfDec) {
  if (!isArray(numArr)) {
    return false;
  }
  var avg = getAverageFromNumArr(numArr, numOfDec),
    i = numArr.length,
    v = 0;
  while (i--) {
    v += Math.pow((numArr[i] - avg), 2);
  }
  v /= numArr.length;
  return getNumWithSetDec(v, numOfDec);
};

function getStandardDeviation(numArr, numOfDec) {
  if (!isArray(numArr)) {
    return false;
  }
  var stdDev = Math.sqrt(getVariance(numArr, numOfDec));
  return getNumWithSetDec(stdDev, numOfDec);
};

function Array_max(array) {
  return Math.max.apply(Math, array);
};

function Array_min(array) {
  return Math.min.apply(Math, array);
};
// BENCHMARKING ENDS
function colorize_boxes_like_in_svg() {
  var bg_color = $("#p").css("background-color");
  bg_color = new RGBColor(bg_color);
  var fill_color, stroke_color, fill_opacity, stroke_opacity, box;
  for (var i = 1; i <= 3; i++) {
    fill_color = $("#p" + i).css("fill");
    fill_color = new RGBColor(fill_color);
    fill_opacity = $("#p" + i).css("fill-opacity");
    fill_color = fill_color.flattenRGBA(fill_opacity, bg_color);
    stroke_color = $("#p" + i).css("stroke");
    stroke_color = new RGBColor(stroke_color);
    stroke_opacity = $("#p" + i).css("stroke-opacity");
    stroke_color = stroke_color.flattenRGBA(stroke_opacity, bg_color);
    if (i == 1) box = "#subject_box";
    else if (i == 2) box = "#clip_box";
    else if (i == 3) box = "#solution_box";
    $(box).css("background-color", fill_color);
  }
}
// The following class exists only for transferring color values from SVG to html table
// It could be got without this, but wanted to try this.
// May be I remove this and make precalculated values
/**
 * A class to parse color values
 * @author Stoyan Stefanov <sstoo@gmail.com>
 * @link   https://www.phpied.com/rgb-color-parser-in-javascript/
 * @license Use it if you like it
 */
function RGBColor(color_string) {
  if (typeof (color_string) == "undefined") color_string = "";
  this.ok = false;
  // strip any leading #
  if (color_string.charAt(0) == '#') { // remove # if any
    color_string = color_string.substr(1, 6);
  }
  color_string = color_string.replace(/ /g, '');
  color_string = color_string.toLowerCase();
  // before getting into regexps, try simple matches
  // and overwrite the input
  var simple_colors = {
    aliceblue: 'f0f8ff',
    antiquewhite: 'faebd7',
    aqua: '00ffff',
    aquamarine: '7fffd4',
    azure: 'f0ffff',
    beige: 'f5f5dc',
    bisque: 'ffe4c4',
    black: '000000',
    blanchedalmond: 'ffebcd',
    blue: '0000ff',
    blueviolet: '8a2be2',
    brown: 'a52a2a',
    burlywood: 'deb887',
    cadetblue: '5f9ea0',
    chartreuse: '7fff00',
    chocolate: 'd2691e',
    coral: 'ff7f50',
    cornflowerblue: '6495ed',
    cornsilk: 'fff8dc',
    crimson: 'dc143c',
    cyan: '00ffff',
    darkblue: '00008b',
    darkcyan: '008b8b',
    darkgoldenrod: 'b8860b',
    darkgray: 'a9a9a9',
    darkgreen: '006400',
    darkkhaki: 'bdb76b',
    darkmagenta: '8b008b',
    darkolivegreen: '556b2f',
    darkorange: 'ff8c00',
    darkorchid: '9932cc',
    darkred: '8b0000',
    darksalmon: 'e9967a',
    darkseagreen: '8fbc8f',
    darkslateblue: '483d8b',
    darkslategray: '2f4f4f',
    darkturquoise: '00ced1',
    darkviolet: '9400d3',
    deeppink: 'ff1493',
    deepskyblue: '00bfff',
    dimgray: '696969',
    dodgerblue: '1e90ff',
    feldspar: 'd19275',
    firebrick: 'b22222',
    floralwhite: 'fffaf0',
    forestgreen: '228b22',
    fuchsia: 'ff00ff',
    gainsboro: 'dcdcdc',
    ghostwhite: 'f8f8ff',
    gold: 'ffd700',
    goldenrod: 'daa520',
    gray: '808080',
    green: '008000',
    greenyellow: 'adff2f',
    honeydew: 'f0fff0',
    hotpink: 'ff69b4',
    indianred: 'cd5c5c',
    indigo: '4b0082',
    ivory: 'fffff0',
    khaki: 'f0e68c',
    lavender: 'e6e6fa',
    lavenderblush: 'fff0f5',
    lawngreen: '7cfc00',
    lemonchiffon: 'fffacd',
    lightblue: 'add8e6',
    lightcoral: 'f08080',
    lightcyan: 'e0ffff',
    lightgoldenrodyellow: 'fafad2',
    lightgrey: 'd3d3d3',
    lightgreen: '90ee90',
    lightpink: 'ffb6c1',
    lightsalmon: 'ffa07a',
    lightseagreen: '20b2aa',
    lightskyblue: '87cefa',
    lightslateblue: '8470ff',
    lightslategray: '778899',
    lightsteelblue: 'b0c4de',
    lightyellow: 'ffffe0',
    lime: '00ff00',
    limegreen: '32cd32',
    linen: 'faf0e6',
    magenta: 'ff00ff',
    maroon: '800000',
    mediumaquamarine: '66cdaa',
    mediumblue: '0000cd',
    mediumorchid: 'ba55d3',
    mediumpurple: '9370d8',
    mediumseagreen: '3cb371',
    mediumslateblue: '7b68ee',
    mediumspringgreen: '00fa9a',
    mediumturquoise: '48d1cc',
    mediumvioletred: 'c71585',
    midnightblue: '191970',
    mintcream: 'f5fffa',
    mistyrose: 'ffe4e1',
    moccasin: 'ffe4b5',
    navajowhite: 'ffdead',
    navy: '000080',
    oldlace: 'fdf5e6',
    olive: '808000',
    olivedrab: '6b8e23',
    orange: 'ffa500',
    orangered: 'ff4500',
    orchid: 'da70d6',
    palegoldenrod: 'eee8aa',
    palegreen: '98fb98',
    paleturquoise: 'afeeee',
    palevioletred: 'd87093',
    papayawhip: 'ffefd5',
    peachpuff: 'ffdab9',
    peru: 'cd853f',
    pink: 'ffc0cb',
    plum: 'dda0dd',
    powderblue: 'b0e0e6',
    purple: '800080',
    red: 'ff0000',
    rosybrown: 'bc8f8f',
    royalblue: '4169e1',
    saddlebrown: '8b4513',
    salmon: 'fa8072',
    sandybrown: 'f4a460',
    seagreen: '2e8b57',
    seashell: 'fff5ee',
    sienna: 'a0522d',
    silver: 'c0c0c0',
    skyblue: '87ceeb',
    slateblue: '6a5acd',
    slategray: '708090',
    snow: 'fffafa',
    springgreen: '00ff7f',
    steelblue: '4682b4',
    tan: 'd2b48c',
    teal: '008080',
    thistle: 'd8bfd8',
    tomato: 'ff6347',
    turquoise: '40e0d0',
    violet: 'ee82ee',
    violetred: 'd02090',
    wheat: 'f5deb3',
    white: 'ffffff',
    whitesmoke: 'f5f5f5',
    yellow: 'ffff00',
    yellowgreen: '9acd32'
  };
  for (var key in simple_colors) {
    if (color_string == key) {
      color_string = simple_colors[key];
    }
  }
  // emd of simple type-in colors
  // array of color definition objects
  var color_defs = [
    {
      re: /^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/,
      example: ['rgb(123, 234, 45)', 'rgb(255,234,245)'],
      process: function (bits) {
        return [
          parseInt(bits[1], 10),
          parseInt(bits[2], 10),
          parseInt(bits[3], 10)];
      }
  },
    {
      re: /^(\w{2})(\w{2})(\w{2})$/,
      example: ['#00ff00', '336699'],
      process: function (bits) {
        return [
          parseInt(bits[1], 16),
          parseInt(bits[2], 16),
          parseInt(bits[3], 16)];
      }
  },
    {
      re: /^(\w{1})(\w{1})(\w{1})$/,
      example: ['#fb0', 'f0f'],
      process: function (bits) {
        return [
          parseInt(bits[1] + bits[1], 16),
          parseInt(bits[2] + bits[2], 16),
          parseInt(bits[3] + bits[3], 16)];
      }
  }];
  // search through the definitions to find a match
  for (var i = 0; i < color_defs.length; i++) {
    var re = color_defs[i].re;
    var processor = color_defs[i].process;
    var bits = re.exec(color_string);
    if (bits) {
      channels = processor(bits);
      this.r = channels[0];
      this.g = channels[1];
      this.b = channels[2];
      this.ok = true;
    }
  }
  // validate/cleanup values
  this.r = (this.r < 0 || isNaN(this.r)) ? 0 : ((this.r > 255) ? 255 : this.r);
  this.g = (this.g < 0 || isNaN(this.g)) ? 0 : ((this.g > 255) ? 255 : this.g);
  this.b = (this.b < 0 || isNaN(this.b)) ? 0 : ((this.b > 255) ? 255 : this.b);
  // some getters
  this.toRGB = function () {
    return 'rgb(' + this.r + ', ' + this.g + ', ' + this.b + ')';
  };
  this.toHex = function () {
    var r = this.r.toString(16);
    var g = this.g.toString(16);
    var b = this.b.toString(16);
    if (r.length == 1) r = '0' + r;
    if (g.length == 1) g = '0' + g;
    if (b.length == 1) b = '0' + b;
    return '#' + r + g + b;
  };
  this.flattenRGBA = function (a, bg) {
    var alpha = 1 - parseFloat(a);
    this.r = Math.round((a * (this.r / 255) + (alpha * (bg.r / 255))) * 255);
    this.g = Math.round((a * (this.g / 255) + (alpha * (bg.g / 255))) * 255);
    this.b = Math.round((a * (this.b / 255) + (alpha * (bg.b / 255))) * 255);
    return this.toHex();
  };
}
// Not used, because using mouse and repeated events is better
function offset_key(e) {
  return;
  if (e.which == 38) {
    make_offset(1);
  }
  else if (e.which == 40) {
    make_offset(-1);
  }
  e.stopPropagation();
  e.preventDefault();
}

function round_to(num, dec) {
  if (typeof (num) == "undefined" || typeof (dec) == "undefined" || isNaN(dec)) {
    alert("Cannot round other than number");
    return false;
  }
  else return Math.round(num * Math.pow(10, dec)) / Math.pow(10, dec);
}

function update_enlarged_SVG_if_needed() {
  if (update_enlarged_SVG) {
    show_svg_source_click("non_click");
    svg_source_enlarge();
  }
  else if (update_enlarged_SVG_source && !update_enlarged_SVG) {
    show_svg_source_click("non_click");
  }
}

function to_printable(a) {
  return a.toFixed(3);
}

function get_polys(scale_again) {
  var polygon = parseInt($('input[type="radio"][name="polygons"]:checked').val(), 10);
  var polys;
  if (polygon === 0) {
    polys = get_gb_and_arrow();
    ss = polys.ss;
    cc = polys.cc;
  }
  else if (polygon == 1) {
    polys = get_text_polys();
    ss = polys.ss;
    cc = polys.cc;
  }
  else if (polygon == 2) {
    polys = get_rectangle_polys();
    ss = polys.ss;
    cc = polys.cc;
  }
  else if (polygon == 3) {
    polys = get_ZigZag_polys();
    ss = polys.ss;
    cc = polys.cc;
  }
  else if (polygon == 6) {
    polys = get_star_and_rect();
    ss = polys.ss;
    cc = polys.cc;
  }
  else if (polygon == 7) {
    polys = get_spiral_and_rects();
    ss = polys.ss;
    cc = polys.cc;
  }
  else if (polygon == 8) {
    polys = get_rounded_grid_and_star();
    ss = polys.ss;
    cc = polys.cc;
  }
  else if (polygon == 9) {
    polys = get_glyph_and_grid();
    ss = polys.ss;
    cc = polys.cc;
  }
  else if (polygon == 10) {
    polys = get_custom_poly();
    ss = polys.ss;
    cc = polys.cc;
  }
  else if (polygon == 4 || polygon == 5) {
    if (!random_subj) random_subj = get_random_polys("subj");
    if (!random_clip) random_clip = get_random_polys("clip");
    if (scale_again) {
      random_subj = scale_again_random_poly(random_subj);
      random_clip = scale_again_random_poly(random_clip);
      ss = random_subj;
      cc = random_clip;
      rnd_sett.scale = scale;
    }
    else {
      ss = random_subj;
      cc = random_clip;
    }
  }
  else if (polygon == 11) {
    if (!random_grid_subj) random_grid_subj = get_random_grids("subj", rnd_grid_sett.random_grid_type);
    if (scale_again) {
      scale_again_random_grid_poly(random_grid_subj);
      ss = random_grid_subj;
      cc = [];
      rnd_grid_sett.scale = scale;
    }
    else {
      ss = random_grid_subj;
      cc = [];
    }
  }
  sss = [
    []
  ];
}
// Main function which setups defaults
// and attach events
// and finally draw the default svg image
function main() {
  // formats internal representation of polygons to
  // specified output format and prints them on input fields
  $("#output_format").change(function () {
    var selected_val = parseInt($(this).val(), 10);
    output_format = selected_val;
    if ($("#custom_polygons_fieldset").css("display") != "none") {
      var subj = $("#custom_polygon_subj").val();
      var clip = $("#custom_polygon_clip").val();
      subj = normalize_clipper_poly(subj, true); // quiet
      clip = normalize_clipper_poly(clip, true); // quiet
      if (subj !== false && clip !== false) {
        $("#custom_polygon_subj").val(format_output(subj));
        $("#custom_polygon_clip").val(format_output(clip));
      }
    }
    var polygon_explorer_string = $("#polygon_explorer_string_inp").val();
    polygon_explorer_string = normalize_clipper_poly(polygon_explorer_string, true); // quiet
    if (polygon_explorer_string !== false) {
      $("#polygon_explorer_string_inp").val(format_output(polygon_explorer_string));
    }
  });
  $("#sample_custom_polygon").change(function () {
    var polygon = parseInt($(this).val(), 10);
    var polys;
    global_do_not_round_and_scale = true;
    var subj = "",
      clip = "";
    if (polygon === "") {
      subj = "";
      clip = "";
    }
    else if (polygon === 0) {
      polys = get_gb_and_arrow();
      subj = polys.ss;
      clip = polys.cc;
    }
    else if (polygon == 1) {
      polys = get_text_polys();
      subj = polys.ss;
      clip = polys.cc;
    }
    else if (polygon == 2) {
      polys = get_rectangle_polys();
      subj = polys.ss;
      clip = polys.cc;
    }
    else if (polygon == 3) {
      polys = get_ZigZag_polys();
      subj = polys.ss;
      clip = polys.cc;
    }
    else if (polygon == 4 || polygon == 5) {
      subj = get_random_polys("subj", polygon);
      clip = get_random_polys("clip", polygon);
    }
    else if (polygon == 6) {
      polys = get_star_and_rect();
      subj = polys.ss;
      clip = polys.cc;
    }
    else if (polygon == 7) {
      polys = get_spiral_and_rects();
      subj = polys.ss;
      clip = polys.cc;
    }
    else if (polygon == 8) {
      polys = get_rounded_grid_and_star();
      subj = polys.ss;
      clip = polys.cc;
    }
    else if (polygon == 9) {
      polys = get_glyph_and_grid();
      subj = polys.ss;
      clip = polys.cc;
    }
    else if (polygon == 10) {
      polys = get_custom_poly();
      subj = polys.ss;
      clip = polys.cc;
    }
    else if (polygon == 12) {
      polys = get_collinear_test();
      subj = polys.ss;
      clip = polys.cc;
    }
    else if (polygon == 13) {
      polys = get_spiral_strips();
      subj = polys.ss;
      clip = polys.cc;
    }
    global_do_not_round_and_scale = false;
    if (subj != "") subj = JSON.stringify(subj);
    if (clip != "") clip = JSON.stringify(clip);
    $("#custom_polygon_subj").val(format_output(subj));
    $("#custom_polygon_clip").val(format_output(clip));
  });
  $("#help_custom_polygon").click(function () {
    var txt = "";
    txt += 'A) You can add your own custom polygons in several formats:\n\n';
    txt += '1) The program uses as an inner default the following format: JSON-stringified array of arrays of point objects eg. [[{"X":100,"Y":100},{"X":200,"Y":100},{"X":200,"Y":200},{"X":100,"Y":200}],[{"X":110,"Y":110},{"X":210,"Y":110},{"X":210,"Y":210},{"X":110,"Y":210}]]. This format allows to input sub polygons. Each sub polygon is an array of point objects. This format makes it easy to transfer polygons to other programs that use Clipper library and is suitable for storing polygons in database.\n\n';
    txt += '2) JSON-stringified array of point objects eg. [{"X":100,"Y":100},{"X":200,"Y":100},{"X":200,"Y":200},{"X":100,"Y":200}]. This format doesn\'t allow to input sub polygons.\n\n';
    txt += '3) JSON-stringified array of arrays of coordinates without "X" and "Y" eg. [[100,100,200,100,200,200,100,200],[110,110,210,110,210,210,110,210]]. This format allows to input sub polygons. Each sub polygon is an array of coordinates so that each x coordinate is followed by an y coordinate. This format makes it easy to transfer polygons to other programs that use Clipper library and is suitable for storing polygons in database.\n\n';
    txt += '4) JSON-stringified array of x and y coordinates eg. [100,100,200,100,200,200,100,200] or [100 100 200 100 200 200 100 200] or [100 100,200 100,200 200,100 200] or the same without []:s. This format doesn\'t allow to input sub polygons.\n\n';
    txt += '5) SVG path strings with commands MLVHZ or mlvhz eg. M100,100 L200,100 L200,200 L100,200Z M110,110 L210,110 L210,210 L110,210Z. This format allows to input sub polygons. Each subpolygon starts with M (moveto) command.\n\n';
    txt += '\n';
    txt += 'B) Custom polygons are saved in browser\'s Local Storage, so they should be tolerant for page reload and browser crashes.\n';
    txt += '\n';
    alert(txt);
  });
  $("#help_builtin_polygon_sets").click(function () {
    var txt = '';
    txt += 'Builtin polygon sets\n\n';
    txt += 'You can add builtin polygon sets into Subj and Clip input fields to edit copies of them.\n\n';
    txt += 'Note! Before saving, nothing happens. After saving, the SVG window is also updated.';
    alert(txt);
  });
  $("#help_output_format").click(function () {
    var txt = '';
    txt += 'Output format\n\n';
    txt += 'To change the polygon coordinate output format please use the above dropdown. The available formats are:\n\n';
    txt += '- Clipper: [[{"X":100,"Y":100},{"X":200,"Y":200}]]\n\n';
    txt += '- Plain: [[100,100,200,200]]\n\n';
    txt += '- SVG: M100,100L200,200Z\n\n';
    txt += 'There are two places where this has effect: 1) the above text box 2) the Subj and Clip text boxes in Custom Polygon fieldset.';
    alert(txt);
  });
  $("#remove_custom_polygon").click(function () {
    var selected_value = $("#custom_polygons_select").val();
    if (selected_value + "" === "0") {
      alert("Cannot remove the default polygon.");
    }
    else if (selected_value) {
      if (confirm("Remove custom polygon " + selected_value + "?")) {
        var arr = $.totalStorage('custom_polygons');
        arr[selected_value] = null;
        $.totalStorage('custom_polygons', arr);
        update_custom_polygons_select();
      }
    }
    else alert("Nothing removable.");
  });
  $("#removeall_custom_polygon").click(function () {
    var count = $("#custom_polygons_select option").length;
    if (count > 1) {
      if (confirm("Remove all " + (count - 1) + " custom polygons?")) {
        $.totalStorage('custom_polygons', []);
        set_default_custom_polygon();
        update_custom_polygons_select();
      }
    }
    else alert("Nothing removable.");
  });
  $("#save_custom_polygon").click(function (e) {
    var subj = $("#custom_polygon_subj").val();
    var clip = $("#custom_polygon_clip").val();
    subj = normalize_clipper_poly(subj);
    clip = normalize_clipper_poly(clip);
    if (subj === false || clip === false) return false;
    if (typeof ($.totalStorage('custom_polygons')) == "undefined" || $.totalStorage('custom_polygons') === null) {
      set_default_custom_polygon();
    }
    var polygon_set = {};
    polygon_set.subj = subj;
    polygon_set.clip = clip;
    var arr2 = $.totalStorage('custom_polygons');
    var selected_value = $("#custom_polygons_select").val();
    if (selected_value + "" !== "0" && selected_value) {
      arr2[selected_value] = polygon_set;
      $.totalStorage('custom_polygons', arr2);
      update_custom_polygons_select();
      show_alert(e, this, "Polygon " + (selected_value) + " updated!");
    }
    else if (selected_value + "" === "0") {
      alert("The default custom polygon cannot be overwrited. If you want to modify it, save it first as a new.");
    }
    else alert("Polygon update failed!");
  });
  $("#add_as_new_custom_polygon").click(function (e) {
    var subj = $("#custom_polygon_subj").val();
    var clip = $("#custom_polygon_clip").val();
    subj = normalize_clipper_poly(subj);
    clip = normalize_clipper_poly(clip);
    if (subj === false || clip === false) return false;
    if (typeof ($.totalStorage('custom_polygons')) == "undefined" || $.totalStorage('custom_polygons') === null) {
      set_default_custom_polygon();
    }
    var polygon_set = {};
    polygon_set.subj = subj;
    polygon_set.clip = clip;
    var arr2 = $.totalStorage('custom_polygons');
    arr2.push(polygon_set);
    $.totalStorage('custom_polygons', arr2);
    update_custom_polygons_select();
    $('#custom_polygons_select').val(arr2.length - 1).change();
    show_alert(e, this, "New polygon " + (arr2.length - 1) + " added!");
  });
  $("#custom_polygons_select").change(function () {
    var selected_value = $("#custom_polygons_select").val();
    if (!selected_value && selected_value + "" !== "0") selected_value = 0;
    var arr = $.totalStorage('custom_polygons');
    if (isArray(arr) && arr.length && typeof (arr[selected_value]) != "undefined") {
      $("#custom_polygon_subj").val(format_output(arr[selected_value].subj));
      $("#custom_polygon_clip").val(format_output(arr[selected_value].clip));
      make_clip();
    }
  });
  $("input[type='radio'][name='polygons']").change(function (e) {
    clipType = clipType_default;
    $('input[type="radio"][name="clipType"][value="' + clipType + '"]').attr('checked', 'checked');
    var val = parseInt($(this).val(), 10);
    selected_polygon_set = val;
    if (val == 10) {
      $("#random_polygons_fieldset").css("display", "none");
      $("#random_grids_fieldset").css("display", "none");
      $("#custom_polygons_fieldset").css("display", "block");
      set_default_custom_polygon();
      update_custom_polygons_select();
      update_fieldset_heights();
      $("#custom_polygons_select").change();
      make_clip();
      return;
    }
    else $("#custom_polygons_fieldset").css("display", "none");
    if (val == 11) {
      clipType = 1;
      $('input[type="radio"][name="clipType"][value="' + clipType + '"]').attr('checked', 'checked');
      $("#random_polygons_fieldset").css("display", "none");
      $("#custom_polygons_fieldset").css("display", "none");
      $("#random_grids_fieldset").css("display", "block");
      random_grid_subj = get_random_grids("subj", rnd_grid_sett.random_grid_type);
      scale_again_random_grid_poly(random_grid_subj);
      update_fieldset_heights();
      make_clip();
      return;
    }
    else {
      $("#custom_polygons_fieldset").css("display", "none");
      $("#random_grids_fieldset").css("display", "none");
    }
    if (val == 3) {
      clipType = 1;
      $('input[type="radio"][name="clipType"][value="' + clipType + '"]').attr('checked', 'checked');
    }
    else
    if (val == 4 || val == 5) {
      $("#random_polygons_fieldset").css("display", "block");
      if (val == 4) rnd_sett_defaults.current = "rect";
      else rnd_sett_defaults.current = "norm";
      // Test for ranges
      if (rnd_sett.clip_point_count < rnd_sett_defaults[rnd_sett_defaults.current].min.clip_point_count) rnd_sett.clip_point_count = rnd_sett_defaults[rnd_sett_defaults.current].min.clip_point_count;
      if (rnd_sett.clip_point_count > rnd_sett_defaults[rnd_sett_defaults.current].max.clip_point_count) rnd_sett.clip_point_count = rnd_sett_defaults[rnd_sett_defaults.current].max.clip_point_count;
      $('#clip_point_count').val(rnd_sett.clip_point_count);
      if (rnd_sett.clip_polygon_count < rnd_sett_defaults[rnd_sett_defaults.current].min.clip_polygon_count) rnd_sett.clip_polygon_count = rnd_sett_defaults[rnd_sett_defaults.current].min.clip_polygon_count;
      if (rnd_sett.clip_polygon_count > rnd_sett_defaults[rnd_sett_defaults.current].max.clip_polygon_count) rnd_sett.clip_polygon_count = rnd_sett_defaults[rnd_sett_defaults.current].max.clip_polygon_count;
      $('#clip_polygon_count').val(rnd_sett.clip_polygon_count);
      if (rnd_sett.subj_point_count < rnd_sett_defaults[rnd_sett_defaults.current].min.subj_point_count) rnd_sett.subj_point_count = rnd_sett_defaults[rnd_sett_defaults.current].min.subj_point_count;
      if (rnd_sett.subj_point_count > rnd_sett_defaults[rnd_sett_defaults.current].max.subj_point_count) rnd_sett.subj_point_count = rnd_sett_defaults[rnd_sett_defaults.current].max.subj_point_count;
      $('#subj_point_count').val(rnd_sett.subj_point_count);
      if (rnd_sett.subj_polygon_count < rnd_sett_defaults[rnd_sett_defaults.current].min.subj_polygon_count) rnd_sett.subj_polygon_count = rnd_sett_defaults[rnd_sett_defaults.current].min.subj_polygon_count;
      if (rnd_sett.subj_polygon_count > rnd_sett_defaults[rnd_sett_defaults.current].max.subj_polygon_count) rnd_sett.subj_polygon_count = rnd_sett_defaults[rnd_sett_defaults.current].max.subj_polygon_count;
      $('#subj_polygon_count').val(rnd_sett.subj_polygon_count);
      random_subj = get_random_polys("subj", val);
      random_clip = get_random_polys("clip", val);
    }
    else $("#random_polygons_fieldset").css("display", "none");
    make_clip();
    update_fieldset_heights();
  });
  $('#generate_random_polygons').hold(function (e) {
    random_subj = get_random_polys("subj");
    random_clip = get_random_polys("clip");
    make_clip();
  }, {
    duration: 300,
    speed: 0,
    min: 150
  });
  $("input[name='clipType']").change(function () {
    if ($('input[type="radio"][name="clipType"][value=""]').is(":checked")) {
      $('input[type="radio"][name="offsettable_poly"][value="1"]').attr('checked', 'checked');
      offsettable_poly = 1;
    }
    else {
      $("#offsettable_poly3").attr('checked', 'checked');
      offsettable_poly = 3;
    }
    clipType = $('input[type="radio"][name="clipType"]:checked').val();
    if (clipType !== "") clipType = parseInt(clipType, 10);
    make_clip();
  });
  $("input[name='subject_fillType']").change(function () {
    subject_fillType = parseInt(this.value, 10);
    make_clip();
  });
  $("input[name='clip_fillType']").change(function () {
    clip_fillType = parseInt(this.value, 10);
    make_clip();
  });
  $("input[name='offsettable_poly']").change(function () {
    offsettable_poly = parseInt(this.value, 10);
    // When offsettable poly is set to Subject or Clip, then boolean operations are not done.
    // To show this to user, set clipType to "No"
    if (offsettable_poly == 1 || offsettable_poly == 2) {
      $('input[type="radio"][name="clipType"][value=""]').attr('checked', 'checked');
      clipType = "";
    }
    make_clip();
  });
  $('#scale_minus').hold(function () {
    var scale_orig = $('#scale').val();
    if (scale_orig && !isNaN(scale_orig) && parseInt(scale_orig, 10).toString() !== "0") scale = parseFloat(scale_orig);
    scale = scale - scale_addition;
    scale = Math.round(scale / scale_addition) * scale_addition;
    if (scale <= 0) scale = 1.0;
    $('#scale').val(to_printable(scale));
    $('#scale').trigger('change');
  }, {
    duration: 300,
    speed: 0,
    min: 150
  });
  $('#scale').change(function () {
    if (this.value && !isNaN(this.value) && parseInt(this.value, 10).toString() !== "0") {
      scale = parseFloat(this.value);
      get_polys(true);
      sss = cc;
      make_clip();
    }
    else alert("Scale has to be a number");
  });
  $('#scale_plus').hold(function () {
    var scale_orig = $('#scale').val();
    if (scale_orig && !isNaN(scale_orig) && parseInt(scale_orig, 10).toString() !== "0") scale = parseFloat(scale_orig);
    scale = scale + scale_addition;
    scale = Math.round(scale / scale_addition) * scale_addition;
    $('#scale').val(to_printable(scale));
    $('#scale').trigger('change');
  }, {
    duration: 300,
    speed: 0,
    min: 150
  });
  $('#delta_minus').hold(function () {
    var delta_orig = $('#delta').val();
    if (!isNaN(delta_orig)) delta = parseFloat(delta_orig);
    delta = delta - 1;
    make_clip();
  }, {
    duration: 300,
    speed: 0,
    min: 150
  });
  $('#delta').change(function (e) {
    var delta_orig = $('#delta').val();
    if (!isNaN(delta_orig)) delta = parseFloat(delta_orig);
    make_clip();
  });
  $('#delta_plus').hold(function () {
    var delta_orig = $('#delta').val();
    if (!isNaN(delta_orig)) delta = parseFloat(delta_orig);
    delta = delta + 1;
    make_clip();
  }, {
    duration: 300,
    speed: 0,
    min: 150
  });
  $("input[name='joinType']").change(function () {
    joinType = parseInt(this.value, 10);
    //make_offset();
    make_clip();
  });
  $("input[name='endType']").change(function () {
    endType = parseInt(this.value, 10);
    //make_offset();
    make_clip();
  });
  $('#miterLimit_minus').hold(function () {
    var miterLimit_orig = $('#miterLimit').val();
    if (!isNaN(miterLimit_orig)) miterLimit = parseFloat(miterLimit_orig);
    miterLimit = miterLimit - 0.1;
    if (miterLimit < 1.0) miterLimit = 1.0;
    make_clip();
  }, {
    duration: 300,
    speed: 0,
    min: 150
  });
  $("#miterLimit").change(function () {
    miterLimit = parseFloat(this.value);
    make_clip();
  });
  $('#miterLimit_plus').hold(function () {
    var miterLimit_orig = $('#miterLimit').val();
    if (!isNaN(miterLimit_orig)) miterLimit = parseFloat(miterLimit_orig);
    miterLimit = miterLimit + 0.1;
    if (miterLimit < 1.0) miterLimit = 1.0;
    make_clip();
  }, {
    duration: 300,
    speed: 0,
    min: 150
  });
  $('#arcTolerance_minus').hold(function () {
    var arcTolerance_orig = $('#arcTolerance').val();
    if (!isNaN(arcTolerance_orig)) arcTolerance = parseFloat(arcTolerance_orig);
    arcTolerance = arcTolerance - 0.1;
    if (arcTolerance < 0.001) arcTolerance = 0.001;
    make_clip();
  }, {
    duration: 300,
    speed: 0,
    min: 150
  });
  $("#arcTolerance").change(function () {
    arcTolerance = parseFloat(this.value);
    if (arcTolerance < 0.001) arcTolerance = 0.001;
    make_clip();
  });
  $('#arcTolerance_plus').hold(function () {
    var arcTolerance_orig = $('#arcTolerance').val();
    if (!isNaN(arcTolerance_orig)) arcTolerance = parseFloat(arcTolerance_orig);
    arcTolerance = arcTolerance + 0.1;
    if (arcTolerance < 0.001) arcTolerance = 0.001;
    make_clip();
  }, {
    duration: 300,
    speed: 0,
    min: 150
  });
  $("#cleandelta").change(function () {
    var cleandelta_orig = $(this).val();
    var this_value = parseFloat(this.value);
    if (!isNaN(this_value)) cleandelta = this_value;
    //else cleandelta = cleandelta_orig;
    make_clip();
  });
  $("#lighten_distance").change(function () {
    var lighten_distance_orig = $(this).val();
    var this_value = parseFloat(this.value);
    if (!isNaN(this_value)) lighten_distance = this_value;
    //else lighten_distance = lighten_distance_orig;
    make_clip();
  });
  $("#AutoFix").change(function () {
    if ($(this).attr('checked')) AutoFix = true;
    else AutoFix = false;
    make_clip();
  });
  $("#ExPolygons").change(function () {
    if ($(this).attr('checked')) ExPolygons = true;
    else ExPolygons = false;
    if (SolutionPolygonClicked) $("#polygon_explorer_string_inp").val(format_output(SolutionPolygonString, "ExPolygons"));
    //make_clip();
  });
  $("#Simplify").change(function () {
    if ($(this).attr('checked')) Simplify = true;
    else Simplify = false;
    make_clip();
  });
  $("#clean").change(function () {
    if ($(this).attr('checked')) {
      if ($("#cleandelta").val() + "".trim() == "") {
        cleandelta = cleandelta_default;
        $("#cleandelta").val(cleandelta);
      }
      clean = true;
    }
    else {
      clean = false;
    }
    make_clip();
  });
  $("#lighten").change(function () {
    if ($(this).attr('checked')) lighten = true;
    else lighten = false;
    make_clip();
  });
  $("#lighten").change(function () {
    if ($(this).attr('checked')) {
      if ($("#lighten_distance").val() + "".trim() == "") {
        lighten_distance = lighten_distance_default;
        $("#lighten_distance").val(lighten_distance);
      }
      lighten = true;
    }
    else {
      lighten = false;
    }
    make_clip();
  });
  $("#PreserveCollinear").change(function () {
    if ($(this).attr('checked')) PreserveCollinear = true;
    else PreserveCollinear = false;
    make_clip();
  });
  $("#ReverseSolution").change(function () {
    if ($(this).attr('checked')) ReverseSolution = true;
    else ReverseSolution = false;
    make_clip();
  });
  $("#StrictlySimple").change(function () {
    if ($(this).attr('checked')) StrictlySimple = true;
    else StrictlySimple = false;
    make_clip();
  });
  $("#off_poly_closed").change(function () {
    if ($(this).attr('checked')) off_poly_closed = true;
    else off_poly_closed = false;
    make_clip();
  });
  $('#subj_polygon_count_minus').hold(function () {
    var subj_polygon_count_orig = $('#subj_polygon_count').val();
    if (!isNaN(subj_polygon_count_orig)) rnd_sett.subj_polygon_count = parseFloat(subj_polygon_count_orig);
    rnd_sett.subj_polygon_count = rnd_sett.subj_polygon_count - 1;
    if (rnd_sett.subj_polygon_count < rnd_sett_defaults[rnd_sett_defaults.current].min.subj_polygon_count) rnd_sett.subj_polygon_count = rnd_sett_defaults[rnd_sett_defaults.current].min.subj_polygon_count;
    $('#subj_polygon_count').val(rnd_sett.subj_polygon_count);
    random_subj = get_random_polys("subj");
    random_clip = get_random_polys("clip");
    make_clip();
  }, {
    duration: 300,
    speed: 0,
    min: 150
  });
  $('#subj_polygon_count').change(function (e) {
    var subj_polygon_count_orig = $('#subj_polygon_count').val();
    if (!isNaN(subj_polygon_count_orig)) rnd_sett.subj_polygon_count = parseFloat(subj_polygon_count_orig);
    if (rnd_sett.subj_polygon_count < rnd_sett_defaults[rnd_sett_defaults.current].min.subj_polygon_count) rnd_sett.subj_polygon_count = rnd_sett_defaults[rnd_sett_defaults.current].min.subj_polygon_count;
    if (rnd_sett.subj_polygon_count > rnd_sett_defaults[rnd_sett_defaults.current].max.subj_polygon_count) rnd_sett.subj_polygon_count = rnd_sett_defaults[rnd_sett_defaults.current].max.subj_polygon_count;
    $('#subj_polygon_count').val(rnd_sett.subj_polygon_count);
    random_subj = get_random_polys("subj");
    random_clip = get_random_polys("clip");
    make_clip();
  });
  $('#subj_polygon_count_plus').hold(function () {
    var subj_polygon_count_orig = $('#subj_polygon_count').val();
    if (!isNaN(subj_polygon_count_orig)) rnd_sett.subj_polygon_count = parseFloat(subj_polygon_count_orig);
    rnd_sett.subj_polygon_count = rnd_sett.subj_polygon_count + 1;
    if (rnd_sett.subj_polygon_count > rnd_sett_defaults[rnd_sett_defaults.current].max.subj_polygon_count) rnd_sett.subj_polygon_count = rnd_sett_defaults[rnd_sett_defaults.current].max.subj_polygon_count;
    $('#subj_polygon_count').val(rnd_sett.subj_polygon_count);
    random_subj = get_random_polys("subj");
    random_clip = get_random_polys("clip");
    make_clip();
  }, {
    duration: 300,
    speed: 0,
    min: 150
  });
  $('#subj_point_count_minus').hold(function () {
    var subj_point_count_orig = $('#subj_point_count').val();
    if (!isNaN(subj_point_count_orig)) rnd_sett.subj_point_count = parseFloat(subj_point_count_orig);
    rnd_sett.subj_point_count = rnd_sett.subj_point_count - 1;
    if (rnd_sett.subj_point_count < rnd_sett_defaults[rnd_sett_defaults.current].min.subj_point_count) rnd_sett.subj_point_count = rnd_sett_defaults[rnd_sett_defaults.current].min.subj_point_count;
    $('#subj_point_count').val(rnd_sett.subj_point_count);
    random_subj = get_random_polys("subj");
    random_clip = get_random_polys("clip");
    make_clip();
  }, {
    duration: 300,
    speed: 0,
    min: 150
  });
  $('#subj_point_count').change(function (e) {
    var subj_point_count_orig = $('#subj_point_count').val();
    if (!isNaN(subj_point_count_orig)) rnd_sett.subj_point_count = parseFloat(subj_point_count_orig);
    if (rnd_sett.subj_point_count < rnd_sett_defaults[rnd_sett_defaults.current].min.subj_point_count) rnd_sett.subj_point_count = rnd_sett_defaults[rnd_sett_defaults.current].min.subj_point_count;
    if (rnd_sett.subj_point_count > rnd_sett_defaults[rnd_sett_defaults.current].max.subj_point_count) rnd_sett.subj_point_count = rnd_sett_defaults[rnd_sett_defaults.current].max.subj_point_count;
    $('#subj_point_count').val(rnd_sett.subj_point_count);
    random_subj = get_random_polys("subj");
    random_clip = get_random_polys("clip");
    make_clip();
  });
  $('#subj_point_count_plus').hold(function () {
    var subj_point_count_orig = $('#subj_point_count').val();
    if (!isNaN(subj_point_count_orig)) rnd_sett.subj_point_count = parseFloat(subj_point_count_orig);
    rnd_sett.subj_point_count = rnd_sett.subj_point_count + 1;
    if (rnd_sett.subj_point_count > rnd_sett_defaults[rnd_sett_defaults.current].max.subj_point_count) rnd_sett.subj_point_count = rnd_sett_defaults[rnd_sett_defaults.current].max.subj_point_count;
    $('#subj_point_count').val(rnd_sett.subj_point_count);
    random_subj = get_random_polys("subj");
    random_clip = get_random_polys("clip");
    make_clip();
  }, {
    duration: 300,
    speed: 0,
    min: 150
  });
  $('#clip_polygon_count_minus').hold(function () {
    var clip_polygon_count_orig = $('#clip_polygon_count').val();
    if (!isNaN(clip_polygon_count_orig)) rnd_sett.clip_polygon_count = parseFloat(clip_polygon_count_orig);
    rnd_sett.clip_polygon_count = rnd_sett.clip_polygon_count - 1;
    if (rnd_sett.clip_polygon_count < rnd_sett_defaults[rnd_sett_defaults.current].min.clip_polygon_count) rnd_sett.clip_polygon_count = rnd_sett_defaults[rnd_sett_defaults.current].min.clip_polygon_count;
    $('#clip_polygon_count').val(rnd_sett.clip_polygon_count);
    random_subj = get_random_polys("subj");
    random_clip = get_random_polys("clip");
    make_clip();
  }, {
    duration: 300,
    speed: 0,
    min: 150
  });
  $('#clip_polygon_count').change(function (e) {
    var clip_polygon_count_orig = $('#clip_polygon_count').val();
    if (!isNaN(clip_polygon_count_orig)) rnd_sett.clip_polygon_count = parseFloat(clip_polygon_count_orig);
    if (rnd_sett.clip_polygon_count < rnd_sett_defaults[rnd_sett_defaults.current].min.clip_polygon_count) rnd_sett.clip_polygon_count = rnd_sett_defaults[rnd_sett_defaults.current].min.clip_polygon_count;
    if (rnd_sett.clip_polygon_count > rnd_sett_defaults[rnd_sett_defaults.current].max.clip_polygon_count) rnd_sett.clip_polygon_count = rnd_sett_defaults[rnd_sett_defaults.current].max.clip_polygon_count;
    $('#clip_polygon_count').val(rnd_sett.clip_polygon_count);
    random_subj = get_random_polys("subj");
    random_clip = get_random_polys("clip");
    make_clip();
  });
  $('#clip_polygon_count_plus').hold(function () {
    var clip_polygon_count_orig = $('#clip_polygon_count').val();
    if (!isNaN(clip_polygon_count_orig)) rnd_sett.clip_polygon_count = parseFloat(clip_polygon_count_orig);
    rnd_sett.clip_polygon_count = rnd_sett.clip_polygon_count + 1;
    if (rnd_sett.clip_polygon_count > rnd_sett_defaults[rnd_sett_defaults.current].max.clip_polygon_count) rnd_sett.clip_polygon_count = rnd_sett_defaults[rnd_sett_defaults.current].max.clip_polygon_count;
    $('#clip_polygon_count').val(rnd_sett.clip_polygon_count);
    random_subj = get_random_polys("subj");
    random_clip = get_random_polys("clip");
    make_clip();
  }, {
    duration: 300,
    speed: 0,
    min: 150
  });
  $('#clip_point_count_minus').hold(function () {
    var clip_point_count_orig = $('#clip_point_count').val();
    if (!isNaN(clip_point_count_orig)) rnd_sett.clip_point_count = parseFloat(clip_point_count_orig);
    rnd_sett.clip_point_count = rnd_sett.clip_point_count - 1;
    if (rnd_sett.clip_point_count < rnd_sett_defaults[rnd_sett_defaults.current].min.clip_point_count) rnd_sett.clip_point_count = rnd_sett_defaults[rnd_sett_defaults.current].min.clip_point_count;
    $('#clip_point_count').val(rnd_sett.clip_point_count);
    random_subj = get_random_polys("subj");
    random_clip = get_random_polys("clip");
    make_clip();
  }, {
    duration: 300,
    speed: 0,
    min: 150
  });
  $('#clip_point_count').change(function (e) {
    var clip_point_count_orig = $('#clip_point_count').val();
    if (!isNaN(clip_point_count_orig)) rnd_sett.clip_point_count = parseFloat(clip_point_count_orig);
    if (rnd_sett.clip_point_count < rnd_sett_defaults[rnd_sett_defaults.current].min.clip_point_count) rnd_sett.clip_point_count = rnd_sett_defaults[rnd_sett_defaults.current].min.clip_point_count;
    if (rnd_sett.clip_point_count > rnd_sett_defaults[rnd_sett_defaults.current].max.clip_point_count) rnd_sett.clip_point_count = rnd_sett_defaults[rnd_sett_defaults.current].max.clip_point_count;
    $('#clip_point_count').val(rnd_sett.clip_point_count);
    random_subj = get_random_polys("subj");
    random_clip = get_random_polys("clip");
    make_clip();
  });
  $('#clip_point_count_plus').hold(function () {
    var clip_point_count_orig = $('#clip_point_count').val();
    if (!isNaN(clip_point_count_orig)) rnd_sett.clip_point_count = parseFloat(clip_point_count_orig);
    rnd_sett.clip_point_count = rnd_sett.clip_point_count + 1;
    if (rnd_sett.clip_point_count > rnd_sett_defaults[rnd_sett_defaults.current].max.clip_point_count) rnd_sett.clip_point_count = rnd_sett_defaults[rnd_sett_defaults.current].max.clip_point_count;
    $('#clip_point_count').val(rnd_sett.clip_point_count);
    random_subj = get_random_polys("subj");
    random_clip = get_random_polys("clip");
    make_clip();
  }, {
    duration: 300,
    speed: 0,
    min: 150
  });
  /*        
random_grid_type_minus
random_grid_type
random_grid_type_plus
random_grid_size_minus
random_grid_size
random_grid_size_plus

      var rnd_grid_sett = {
        random_grid_type: 1,
        random_grid_size: 32
      };
*/
  // -------------------------------------------
  // eyup
  $('#generate_random_grids').hold(function (e) {
    random_grid_subj = get_random_grids("subj", rnd_grid_sett.random_grid_type);
    make_clip();
  }, {
    duration: 300,
    speed: 0,
    min: 150
  });
  $('#random_grid_type_minus').hold(function () {
    var random_grid_type_orig = $('#random_grid_type').val();
    rnd_grid_sett.random_grid_type--;
    if (rnd_grid_sett.random_grid_type < 1) rnd_grid_sett.random_grid_type = 3;
    $('#random_grid_type').val(rnd_grid_sett.random_grid_type);
    random_grid_subj = get_random_grids("subj", rnd_grid_sett.random_grid_type);
    make_clip();
  }, {
    duration: 300,
    speed: 0,
    min: 150
  });
  $('#random_grid_type').change(function (e) {
    var random_grid_type_orig = parseInt($('#random_grid_type').val(), 10);
    if (rnd_grid_sett.random_grid_type > 3) rnd_grid_sett.random_grid_type = 3;
    if (rnd_grid_sett.random_grid_type < 1) rnd_grid_sett.random_grid_type = 1;
    if (!isNaN(rnd_grid_sett.random_grid_type)) rnd_grid_sett.random_grid_type = parseFloat(rnd_grid_sett.random_grid_type);
    //$('#random_grid_type').val(rnd_grid_sett.random_grid_type);
    random_grid_subj = get_random_grids("subj", rnd_grid_sett.random_grid_type);
    make_clip();
  });
  $('#random_grid_type_plus').hold(function () {
    var random_grid_type_orig = $('#random_grid_type').val();
    rnd_grid_sett.random_grid_type++;
    if (rnd_grid_sett.random_grid_type > 3) rnd_grid_sett.random_grid_type = 1;
    $('#random_grid_type').val(rnd_grid_sett.random_grid_type);
    random_grid_subj = get_random_grids("subj", rnd_grid_sett.random_grid_type);
    make_clip();
  }, {
    duration: 300,
    speed: 0,
    min: 150
  });
  $('#transform_rotation_minus').hold(function () {
    var transform_rotation_orig = parseInt($('#transform_rotation').val(), 10);
    transform.rotation = transform.rotation - 1;
    if (transform.rotation < -360) transform.rotation = 360;
    $('#transform_rotation').val(transform.rotation);
    transform.distort = null;
    make_clip();
  }, {
    duration: 300,
    speed: 0,
    min: 150
  });
  $('#transform_rotation').change(function (e) {
    var transform_rotation_orig = $('#transform_rotation').val();
    if (!isNaN(transform.rotation)) transform.rotation = parseFloat(transform_rotation_orig);
    if (transform.rotation < -360) transform.rotation = -360;
    if (transform.rotation > 360) transform.rotation = 360;
    $('#transform_rotation').val(transform.rotation);
    transform.distort = null;
    make_clip();
  });
  $('#transform_rotation_plus').hold(function () {
    var transform_rotation_orig = parseInt($('#transform_rotation').val(), 10);
    transform.rotation = transform.rotation + 1;
    if (transform.rotation > 360) transform.rotation = -360;
    $('#transform_rotation').val(transform.rotation);
    transform.distort = null;
    make_clip();
  }, {
    duration: 300,
    speed: 0,
    min: 150
  });
  $('#transform_distort').hold(function (e) {
    transform.distort = "generate_new";
    transform.rotation = 0;
    transform.distort_destination = null;
    $("#transform_rotation").val("0");
    make_clip();
  }, {
    duration: 300,
    speed: 0,
    min: 150
  });
  $('#transform_distort_sample1').click(function (e) {
    transform.distort = "generate_new";
    transform.distort_destination = transform.destination1;
    transform.rotation = 0;
    $("#transform_rotation").val("0");
    make_clip();
  });
  $('#transform_distort_sample2').click(function (e) {
    transform.distort = "generate_new";
    transform.distort_destination = transform.destination2;
    transform.rotation = 0;
    $("#transform_rotation").val("0");
    make_clip();
  });
  $('#transform_reset').click(function (e) {
    transform.distort = null;
    transform.rotation = 0;
    $('#transform_rotation').val(transform.rotation);
    make_clip();
  });
  // -------------------------------------------
  $("#bevel").change(function () {
    if ($(this).attr('checked')) {
      bevel = true;
      $("#p3").attr("filter", "url(#innerbewel)");
    }
    else {
      bevel = false;
      $("#p3").removeAttr("filter");
    }
    update_enlarged_SVG_if_needed();
  });

  function show_svg_source_click_2() {
    show_svg_source_click();
    svg_source_enlarge();
  }
  $("#show_svg_source").click(show_svg_source_click_2);
  $('#sub_poly_links_update').change(function () {
    if ($(this).is(':checked')) {
      sub_poly_links_update = 1;
      if (!p) p = SVG.create();
      if (p1) p1.remove();
      if (p2) p2.remove();
      if (p3) p3.remove();
      SVG.addpaths(ss, cc, off_result, subject_fillType, clip_fillType);
    }
    else {
      sub_poly_links_update = 0;
      $("#subj_subpolygons, #subj_points_in_subpolygons, #subj_points_total, #clip_subpolygons, #clip_points_in_subpolygons, #clip_points_total, #solution_subpolygons, #solution_points_in_subpolygons, #solution_points_total, #points_total, #all_subpolygons, #area").html("");
    }
  });
  $("#output_format").val(output_format);
  myresize();
  $("document").mousedown(function () {
    ismousedown = true;
  });
  $("document").mouseup(function () {
    ismousedown = false;
  });
  $("#benchmark1,#benchmark2,#benchmark1b,#benchmark2b").click(function () {
    clicked_benchmark_button_id = $(this).attr("id");
    if (benchmark_running && !benchmark_automatic_click) {
      for (var lsk = 0; lsk < bench_glob.length; lsk++) {
        clearTimeout(bench_glob[lsk].setTimeout);
      }
      bench_glob.length = 0;
      repeat = 0;
      benchmark_running = 0;
      ClipperLib.MaxSteps = ClipperLib_MaxSteps_original;
      $("#" + clicked_benchmark_button_id).html($("#" + clicked_benchmark_button_id).html().replace("Stop", "Run"));
      $("#" + clicked_benchmark_button_id).attr("title", $("#" + clicked_benchmark_button_id).attr("title").replace("Stop", "Execute"));
      $("button,input,select").removeAttr('disabled');
      $('#sub_poly_links_update').trigger("change");
      return;
    }
    else {
      // disable buttons that are not allowed to click when running
      $("button,input,select").attr('disabled', 'disabled');
      $("#benchmark1,#benchmark2,#benchmark1b,#benchmark2b").each(function () {
        var $this = $(this);
        if ($this.attr("id") != clicked_benchmark_button_id) $this.attr('disabled', 'disabled');
        else $this.removeAttr('disabled');
      });
    }
    benchmark_automatic_click = 0;
    benchmark_running = 1;
    ClipperLib.MaxSteps = 10;
    $("#" + clicked_benchmark_button_id).html($("#" + clicked_benchmark_button_id).html().replace("Run", "Stop"));
    $("#" + clicked_benchmark_button_id).attr("title", $("#" + clicked_benchmark_button_id).attr("title").replace("Execute", "Stop"));
    bench.clear();
    bench.includeSVG = false;
    var scaleLocal;
    var polygon_id;
    var deltaLocal, clipTypeLocal, offsettable_polyLocal_start, offsettable_polyLocal_end;
    var offsettable_polyLocal, joinTypeLocal, endTypeLocal = 4,
      miterLimitLocal, miterLimitLocal_start, miterLimitLocal_end;
    var arcToleranceLocal, arcToleranceLocal_start, arcToleranceLocal_end;
    var fillTypeLocal, fillTypeLocal_start, fillTypeLocal_end;
    var timeout_time = 0;
    var timeout_time_addition = 100;
    window.last_completed_bench = "";
    bench_glob.length = 0;
    timeout_time = 0;
    timeout_time_addition = 0;
    latest_time = 10;
    window.last_completed_bench = "";
    var count;
    var deltaLocals = [-5, 0, 10, 30];
    var deltaLocal_i, m;
    var this_id = $(this).attr("id");
    if (this_id == "benchmark1b" || this_id == "benchmark2b") repeat_times = 5;
    else repeat_times = 1;
    for (count = 0; count < 2; count++) {
      if (this_id == "benchmark1" || this_id == "benchmark1b") {
        scaleLocal = 1;
        if (count === 1) continue;
      }
      else if (this_id == "benchmark2" || this_id == "benchmark2b") {
        scaleLocal = 100000000;
        if (count === 1) continue;
      }
      for (polygon_id = 0; polygon_id < 10; polygon_id++) {
        if (!(polygon_id == 0 || polygon_id == 1 || polygon_id == 7 || polygon_id == 8 || polygon_id == 9)) continue;
        if (polygon_id == 4 || polygon_id == 5) {
          fillTypeLocal_start = 0;
          fillTypeLocal_end = 1;
        }
        else {
          fillTypeLocal_start = 1;
          fillTypeLocal_end = 1
        }
        for (fillTypeLocal = fillTypeLocal_start; fillTypeLocal < fillTypeLocal_end + 1; fillTypeLocal++) {
          for (clipTypeLocal = 0; clipTypeLocal < 5; clipTypeLocal++) {
            if (clipTypeLocal === 0) {
              offsettable_polyLocal_start = 1;
              offsettable_polyLocal_end = 2;
            }
            else {
              offsettable_polyLocal_start = 3;
              offsettable_polyLocal_end = 3;
            }
            for (offsettable_polyLocal = offsettable_polyLocal_start; offsettable_polyLocal < offsettable_polyLocal_end + 1; offsettable_polyLocal++) {
              if (polygon_id === 0 && offsettable_polyLocal == 2) continue;
              for (joinTypeLocal = 0; joinTypeLocal < 3; joinTypeLocal++) {
                for (deltaLocal_i = 0, m = deltaLocals.length; deltaLocal_i < m; deltaLocal_i++) {
                  deltaLocal = deltaLocals[deltaLocal_i];
                  if (joinTypeLocal == 2 && deltaLocal !== 0) {
                    miterLimitLocal_start = 1;
                    miterLimitLocal_end = 6;
                    arcToleranceLocal = 0.25;
                  }
                  else {
                    if (this_id == "benchmark2" || this_id == "benchmark2b") { // big integers
                      miterLimitLocal_start = 1;
                      miterLimitLocal_end = 6;
                      arcToleranceLocal = 0.25;
                    }
                    else { // normal
                      miterLimitLocal_start = 1;
                      miterLimitLocal_end = 6;
                      arcToleranceLocal = 0.25;
                    }
                  }
                  for (miterLimitLocal = miterLimitLocal_start; miterLimitLocal < miterLimitLocal_end + 1; miterLimitLocal += 2) {
                    bench_glob[bench_glob.length] = {
                      polygon_id: polygon_id,
                      joinType: joinTypeLocal, // 0,1
                      endType: endTypeLocal, // 0,1
                      offsettable_poly: offsettable_polyLocal, // 1,2,3
                      delta: deltaLocal, // -10 - 10
                      miterLimit: miterLimitLocal, // 1 - 5
                      arcTolerance: arcToleranceLocal, // 0.25
                      AutoFix: true, // false, true
                      ExPolygons: false, // false, true
                      Simplify: false, // false, true
                      PreserveCollinear: false, // false, true
                      ReverseSolution: false, // false, true
                      StrictlySimple: false, // false, true
                      off_poly_closed: true, // false, true
                      subject_fillType: fillTypeLocal, // 0,1
                      clip_fillType: fillTypeLocal, //0, 1
                      clipType: (clipTypeLocal === 0) ? "" : clipTypeLocal - 1, // "",0,1,2,3
                      scale: scaleLocal, // 100, 100 000, 1000 000 000
                      rnd_sett: {
                        clip_polygon_count: 4,
                        clip_point_count: 4,
                        subj_polygon_count: 4,
                        subj_point_count: 4
                      }
                    };
                    timeout_time += timeout_time_addition;
                    bench_glob[bench_glob.length - 1].setTimeout = setTimeout("benchmark2(" + (bench_glob.length - 1) + ")", timeout_time);
                    bench_glob[bench_glob.length - 1].timeout_time = timeout_time;
                  }
                }
              }
            }
          }
        }
      }
    }
  });
  $('#scale').val(to_printable(scale));
  $('#delta').val(to_printable(delta));
  $('#miterLimit').val(to_printable(miterLimit));
  $('#arcTolerance').val(to_printable(arcTolerance));
  if (AutoFix) $('#AutoFix').attr('checked', 'checked');
  else $('#AutoFix').removeAttr('checked');
  if (ExPolygons) $('#ExPolygons').attr('checked', 'checked');
  else $('#ExPolygons').removeAttr('checked');
  if (Simplify) $('#Simplify').attr('checked', 'checked');
  else $('#Simplify').removeAttr('checked');
  if (clean) $('#clean').attr('checked', 'checked');
  else $('#clean').removeAttr('checked');
  $("#cleandelta").val(cleandelta_default);
  if (lighten) $('#lighten').attr('checked', 'checked');
  else $('#lighten').removeAttr('checked');
  $('#lighten_distance').val(lighten_distance_default);
  if (PreserveCollinear) $('#PreserveCollinear').attr('checked', 'checked');
  else $('#PreserveCollinear').removeAttr('checked');
  if (ReverseSolution) $('#ReverseSolution').attr('checked', 'checked');
  else $('#ReverseSolution').removeAttr('checked');
  if (StrictlySimple) $('#StrictlySimple').attr('checked', 'checked');
  else $('#StrictlySimple').removeAttr('checked');
  if (off_poly_closed) $('#off_poly_closed').attr('checked', 'checked');
  else $('#off_poly_closed').removeAttr('checked');
  $('input[type="radio"][name="clipType"][value="' + clipType + '"]').attr('checked', 'checked');
  $('input[type="radio"][name="subject_fillType"][value="' + subject_fillType + '"]').attr('checked', 'checked');
  $('input[type="radio"][name="clip_fillType"][value="' + clip_fillType + '"]').attr('checked', 'checked');
  $('input[type="radio"][name="polygons"][value="' + polygons_default + '"]').attr('checked', 'checked').change();
  var polygon = parseInt(polygons_default, 10);
  if (polygon == 4) rnd_sett = rnd_sett_defaults.rects.
  default;
  else rnd_sett = rnd_sett_defaults.norm.
  default;
  $('input[type="radio"][name="offsettable_poly"][value="' + offsettable_poly + '"]').attr('checked', 'checked');
  $('#subj_polygon_count').val(rnd_sett.subj_polygon_count);
  $('#subj_point_count').val(rnd_sett.subj_point_count);
  $('#clip_polygon_count').val(rnd_sett.clip_polygon_count);
  $('#clip_point_count').val(rnd_sett.clip_point_count);
  $('#random_grid_type').val(rnd_grid_sett.random_grid_type);
  $('#random_grid_rotation').val(rnd_grid_sett.random_grid_rotation);
  if (bevel) $('#bevel').attr('checked', 'checked');
  else $('#bevel').removeAttr('checked');
  if (sub_poly_links_update) $('#sub_poly_links_update').attr('checked', 'checked');
  else $('#sub_poly_links_update').removeAttr('checked');
  $('input[type="radio"][name="joinType"][value="' + joinType + '"]').attr('checked', 'checked').change();
  $('input[type="radio"][name="endType"][value="' + endType + '"]').attr('checked', 'checked').change();
  make_clip();
  colorize_boxes_like_in_svg();
} // main()
function make_offset() {
  // --------------------------------
  // SELECT OFSETTABLE POLYGON STARTS
  // --------------------------------
  //console.log("make_offset()");
  var off_poly, off_poly1, off_poly2, off_poly3;
  offsettable_poly = parseInt($('input[type="radio"][name="offsettable_poly"]:checked').val(), 10);
  if (offsettable_poly == 1) {
    if (transform.rotation == 0 && transform.distort == null)
      off_poly = ClipperLib.JS.Clone(ss);
    else
      off_poly = ClipperLib.JS.Clone(ss_transformed);
  }
  else if (offsettable_poly == 2) {
    if (transform.rotation === 0 && transform.distort == null)
      off_poly = ClipperLib.JS.Clone(cc);
    else
      off_poly = ClipperLib.JS.Clone(cc_transformed);
  }
  else if (offsettable_poly == 3) {
    off_poly = sss;
  }
  if (typeof (off_poly) == "undefined" || ! (sss instanceof Array)) off_poly = [
    []
  ];
  // ------------------------------
  // SELECT OFSETTABLE POLYGON ENDS
  // ------------------------------
  if (ClipperLib.biginteger_used === null) ClipperLib.biginteger_used = 0;
  // -------------
  // CLEAN STARTS
  // -------------
  if (clean) {
    //off_poly = ClipperLib.Clean(off_poly, cleandelta * scale);
    off_poly = ClipperLib.Clipper.CleanPolygons(off_poly, cleandelta * scale);
  }
  //if (isArray(off_poly) && off_poly.length)
  //console.log(JSON.stringify(off_poly[0]));
  // -------------
  // CLEAN ENDS
  // -------------
  // ---------------
  // SIMPLIFY STARTS
  // ---------------
  // Must simplify before offsetting, to get offsetting right in certain cases.
  // Other operations (boolean ones) doesn't need this.
  // This is needed when offsetting polygons that has selfintersecting parts ( eg. 5-point star needs this )
  if (Simplify) {
    // Simplifying is only needed when offsetting original polys, because
    // results of boolean operations are already simplified.
    // Note! if clip polygon is the same as subject polygon
    // then it seems that simplifying is needed also for result of boolean operation (ie. solution).
    if (offsettable_poly == 1) // subj
    {
      off_poly = ClipperLib.Clipper.SimplifyPolygons(off_poly, subject_fillType);
    }
    if (offsettable_poly == 2) // clip
    {
      off_poly = ClipperLib.Clipper.SimplifyPolygons(off_poly, clip_fillType);
    }
    if (offsettable_poly == 3) // solution
    {
      off_poly = ClipperLib.Clipper.SimplifyPolygons(off_poly, clip_fillType);
      if (subject_fillType !== clip_fillType) {
        console.log("Subject filltype and Clip filltype are different. We used Clip filltype in SimplifyPolygons().");
      }
    }
  }
  // -------------
  // SIMPLIFY ENDS
  // -------------
  // ------------------------------
  // ACTUAL OFFSET OPERATION STARTS
  // ------------------------------
  if (delta) {
    cpr.Clear();
    var param_delta = round_to(delta * scale, 3);
    var param_miterLimit = round_to(miterLimit, 3);
    var param_arcTolerance = round_to(arcTolerance * scale, 3);
    if (param_arcTolerance < 0.0009 * scale) alert("To prevent browser hanging, we don't accept too low arcTolerances.");
    else {
      off_result = new ClipperLib.Paths();
      var B0 = bench.start("Offset", "Offset(" + param_delta + ", " + joinType + ", " + endType + ", " + param_miterLimit + "," + param_arcTolerance + ")");
      var co = new ClipperLib.ClipperOffset(param_miterLimit, param_arcTolerance);
			
			//var f = 10;
			//var widths = [0, 0.6*f, 1*f, 1.5*f, 2*f, 2.5*f, 3*f, 3.5*f, 4*f];
      var widths = window.widths;
      var i, j, z = 0;
      for(i = 0; i < off_poly.length; i++)
	      for(j = 0; j < off_poly[i].length; j++)
      	{
      		off_poly[i][j].Z = widths[z];
      		z++;
      	}
      console.log(off_poly);
      co.AddPaths(off_poly, joinType, endType);
      co.Execute(off_result, param_delta);
      bench.end(B0);
      // off_poly_closed
    }
  }
  else {
    off_result = off_poly;
  }
  // ----------------------------
  // ACTUAL OFFSET OPERATION ENDS
  // ------------------------------
  // ----------------------------
  // LIGHTENING STARTS
  // ------------------------------
  if (lighten) {
    off_result = ClipperLib.JS.Lighten(off_result, lighten_distance * scale);
    // Because lighten may produce self-intersections,
    // must Simplify to be sure that result is free of them,
    // but only if user wants
    if (Simplify) {
      off_result = ClipperLib.Clipper.SimplifyPolygons(off_result, subject_fillType);
    }
  }
  // ----------------------------
  // LIGHTENING ENDS
  // ------------------------------
  // --------------------------------
  // SVG UPDATE STARTS
  // --------------------------------
  if (!p) p = SVG.create();
  if (p1) p1.remove();
  if (p2) p2.remove();
  if (p3) p3.remove();
  if (transform.rotation !== 0 || transform.distort !== null) {
    ss = ss_transformed;
    cc = cc_transformed;
  }
  if (bench.includeSVG) var B3 = bench.start("SVG", "addpaths(ss, cc, off_result,  " + subject_fillType + ", " + clip_fillType + ")");
  SVG.addpaths(ss, cc, off_result, subject_fillType, clip_fillType);
  if (bench.includeSVG) bench.end(B3);
  if (transform.rotation !== 0 || transform.distort !== null) {
    ss = ss_copy;
    cc = cc_copy;
  }
  // --------------------------------
  // SVG UPDATE ENDS
  // --------------------------------
  // --------------------------------
  // UPDATE BIGINTEGERS TOGGLE STARTS
  // --------------------------------
  if (ClipperLib.biginteger_used !== null) {
    if (ClipperLib.biginteger_used) $("#biginteger_used").html("true");
    else $("#biginteger_used").html("false");
  }
  else $("#biginteger_used").html("unknown");
  ClipperLib.biginteger_used = null;
  // ------------------------------
  // UPDATE BIGINTEGERS TOGGLE ENDS
  // ------------------------------
  // UPDATE DELTA TO FORM
  $('#delta').val(to_printable(delta));
  // UPDATE MITERLIMIT TO FORM
  $('#miterLimit').val(to_printable(miterLimit));
  $('#arcTolerance').val(to_printable(arcTolerance));
  // PRINT BENCHMARKS
  $("#benchmark_div").html(bench.print());
  update_enlarged_SVG_if_needed();
  update_fieldset_heights();
}

function transferPoint(p, source, destination) {
  var ADDING = 0.001; // to avoid dividing by zero
  var xI = p.X;
  var yI = p.Y;
  var xA = source[0].X;
  var yA = source[0].Y;
  var xC = source[2].X;
  var yC = source[2].Y;
  var xAu = destination[0].X;
  var yAu = destination[0].Y;
  var xBu = destination[1].X;
  var yBu = destination[1].Y;
  var xCu = destination[2].X;
  var yCu = destination[2].Y;
  var xDu = destination[3].X;
  var yDu = destination[3].Y;
  // Calculations
  // if points are the same, have to add a ADDING to avoid dividing by zero
  if (xBu == xCu) xCu += ADDING;
  if (xAu == xDu) xDu += ADDING;
  if (xAu == xBu) xBu += ADDING;
  if (xDu == xCu) xCu += ADDING;
  var kBC = (yBu - yCu) / (xBu - xCu);
  var kAD = (yAu - yDu) / (xAu - xDu);
  var kAB = (yAu - yBu) / (xAu - xBu);
  var kDC = (yDu - yCu) / (xDu - xCu);
  if (kBC == kAD) kAD += ADDING;
  var xE = (kBC * xBu - kAD * xAu + yAu - yBu) / (kBC - kAD);
  var yE = kBC * (xE - xBu) + yBu;
  if (kAB == kDC) kDC += ADDING;
  var xF = (kAB * xBu - kDC * xCu + yCu - yBu) / (kAB - kDC);
  var yF = kAB * (xF - xBu) + yBu;
  if (xE == xF) xF += ADDING;
  var kEF = (yE - yF) / (xE - xF);
  if (kEF == kAB) kAB += ADDING;
  var xG = (kEF * xDu - kAB * xAu + yAu - yDu) / (kEF - kAB);
  var yG = kEF * (xG - xDu) + yDu;
  if (kEF == kBC) kBC += ADDING;
  var xH = (kEF * xDu - kBC * xBu + yBu - yDu) / (kEF - kBC);
  var yH = kEF * (xH - xDu) + yDu;
  var rG = (yC - yI) / (yC - yA);
  var rH = (xI - xA) / (xC - xA);
  var xJ = (xG - xDu) * rG + xDu;
  var yJ = (yG - yDu) * rG + yDu;
  var xK = (xH - xDu) * rH + xDu;
  var yK = (yH - yDu) * rH + yDu;
  if (xF == xJ) xJ += ADDING;
  if (xE == xK) xK += ADDING;
  var kJF = (yF - yJ) / (xF - xJ); //23
  var kKE = (yE - yK) / (xE - xK); //12
  var xKE;
  if (kJF == kKE) kKE += ADDING;
  var xIu = (kJF * xF - kKE * xE + yE - yF) / (kJF - kKE);
  var yIu = kJF * (xIu - xJ) + yJ;
  p.X = xIu;
  p.Y = yIu;
}

function angle(c, b, a) {
  var ab = {
    X: b.X - a.X,
    Y: b.Y - a.Y
  };
  var cb = {
    X: b.X - c.X,
    Y: b.Y - c.Y
  };
  var dot = (ab.X * cb.X + ab.Y * cb.Y);
  var cross = (ab.X * cb.Y - ab.Y * cb.X);
  var alpha = Math.atan2(cross, dot);
  return alpha * 180 / Math.PI;
}

function isPermissible(p) {
  var p0 = p[0];
  var p1 = p[1];
  var p2 = p[2];
  var p3 = p[3];
  var a0 = angle(p3, p0, p1);
  var a1 = angle(p0, p1, p2);
  var a2 = angle(p1, p2, p3);
  var a3 = angle(p2, p3, p0);
  if (!(a0 > 1 && a0 < 179) || !(a1 > 1 && a1 < 179) || !(a2 > 1 && a2 < 179) || !(a3 > 1 && a3 < 179))
    return false;
  else return true;
}

function rotatePoint(pt, center, cosAngle, sinAngle) {
  var dx = (pt.X - center.X);
  var dy = (pt.Y - center.Y);
  pt.X = center.X + dx * cosAngle - dy * sinAngle;
  pt.Y = center.Y + dx * sinAngle + dy * cosAngle;
  return pt;
}

function make_clip() {
  //console.log("make_clip())");
  ClipperLib.biginteger_used = null;
  if (!cpr) {
    cpr = new ClipperLib.Clipper();
  }
  else cpr.Clear();
  get_polys();
  //if (clipType !== "" && offsettable_poly == 3)
  if (1 == 1) {
    if (PreserveCollinear) cpr.PreserveCollinear = true;
    else cpr.PreserveCollinear = false;
    if (ReverseSolution) cpr.ReverseSolution = true;
    else cpr.ReverseSolution = false;
    if (StrictlySimple) cpr.StrictlySimple = true;
    else cpr.StrictlySimple = false;
    cpr.AddPaths(ss, ClipperLib.PolyType.ptSubject, subj_is_closed);
    cpr.AddPaths(cc, ClipperLib.PolyType.ptClip, true);
    var bounds, center;
    if (transform.rotation === 0) {
      ss_copy = ClipperLib.JS.Clone(ss);
      cc_copy = ClipperLib.JS.Clone(cc);
    }
    else {
      ss_transformed = ClipperLib.JS.Clone(ss);
      cc_transformed = ClipperLib.JS.Clone(cc);
      bounds = ClipperLib.Clipper.GetBounds(ss.concat(cc));
      center = {
        X: Math.round(bounds.left + (bounds.right - bounds.left) / 2),
        Y: Math.round(bounds.top + (bounds.bottom - bounds.top) / 2)
      };
      cpr.Clear();
      var angleRad = (transform.rotation / 180) * Math.PI;
      var cosAngle = Math.cos(angleRad);
      var sinAngle = Math.sin(angleRad);
      for (var i = 0, ilen = ss_transformed.length; i < ilen; i++)
        for (var j = 0, jlen = ss_transformed[i].length; j < jlen; j++)
          rotatePoint(ss_transformed[i][j], center, cosAngle, sinAngle)
      for (var i = 0, ilen = cc_transformed.length; i < ilen; i++)
        for (var j = 0, jlen = cc_transformed[i].length; j < jlen; j++)
          rotatePoint(cc_transformed[i][j], center, cosAngle, sinAngle)
      cpr.Clear();
      cpr.AddPaths(ss_transformed, ClipperLib.PolyType.ptSubject, subj_is_closed);
      cpr.AddPaths(cc_transformed, ClipperLib.PolyType.ptClip, true);
    }
    if (transform.distort === null) {
      ss_copy = ClipperLib.JS.Clone(ss);
      cc_copy = ClipperLib.JS.Clone(cc);
    }
    else {
      ss_transformed = ClipperLib.JS.Clone(ss);
      cc_transformed = ClipperLib.JS.Clone(cc);
      if (typeof (bounds) == "undefined") {
        bounds = ClipperLib.Clipper.GetBounds(ss_transformed.concat(cc_transformed));
        center = {
          X: bounds.left + (bounds.right - bounds.left) / 2,
          Y: bounds.top + (bounds.bottom - bounds.top) / 2
        };
      }
      //console.log("transform.distort:" + transform.distort);
      var source = [
        {
          X: bounds.left,
          Y: bounds.top
      },
        {
          X: bounds.right,
          Y: bounds.top
      },
        {
          X: bounds.right,
          Y: bounds.bottom
      },
        {
          X: bounds.left,
          Y: bounds.bottom
      }];
      if (transform.distort == "generate_new") {
        var centerOffset = 1 * scale;
        var marg = 100 * scale;
        do {
          var destination = [
            {
              X: rand(bounds.left - marg, center.X - centerOffset),
              Y: rand(bounds.top - marg, center.Y - centerOffset)
          },
            {
              X: rand(center.X + centerOffset, bounds.right + marg),
              Y: rand(bounds.top - marg, center.Y - centerOffset)
          },
            {
              X: rand(center.X + centerOffset, bounds.right + marg),
              Y: rand(center.Y + centerOffset, bounds.bottom + marg)
          },
            {
              X: rand(bounds.left - marg, center.X - centerOffset),
              Y: rand(center.Y + centerOffset, bounds.bottom + marg)
          }];
        }
        while (!isPermissible(destination));
        if (transform.distort_destination) destination = transform.distort_destination;
        transform.distort = destination;
      }
      else destination = transform.distort;
      for (var i = 0, ilen = ss_transformed.length; i < ilen; i++)
        for (var j = 0, jlen = ss_transformed[i].length; j < jlen; j++)
          transferPoint(ss_transformed[i][j], source, destination)
      for (var i = 0, ilen = cc_transformed.length; i < ilen; i++)
        for (var j = 0, jlen = cc_transformed[i].length; j < jlen; j++)
          transferPoint(cc_transformed[i][j], source, destination)
          //console.log(ss_transformed);
      cpr.Clear();
      cpr.AddPaths(ss_transformed, ClipperLib.PolyType.ptSubject, subj_is_closed);
      cpr.AddPaths(cc_transformed, ClipperLib.PolyType.ptClip, true);
      // center and scale to fit window
      var padding = 10;
      var bb = ClipperLib.Clipper.GetBounds(ss_transformed.concat(cc_transformed));
      var window_width = 500;
      var window_height = 350;
      var bb_min_x = padding * scale;
      var bb_max_x = window_width * scale - padding;
      var bb_min_y = padding * scale;
      var bb_max_y = window_height * scale - padding;
      var max_width = bb_max_x - bb_min_x;
      var max_height = bb_max_y - bb_min_y;
      var fit_scale_x = (max_width - bb_min_x) / (bb.right - bb.left);
      var fit_scale_y = (max_height - bb_min_x) / (bb.bottom - bb.top);
      var fit_translate_x = (-bb.left + bb_min_x / fit_scale_x);
      var fit_translate_y = (-bb.top + bb_min_y / fit_scale_y);
      for (var i = 0, ilen = ss_transformed.length; i < ilen; i++)
        for (var j = 0, jlen = ss_transformed[i].length; j < jlen; j++) {
          ss_transformed[i][j].X = (ss_transformed[i][j].X + fit_translate_x) * fit_scale_x;
          ss_transformed[i][j].Y = (ss_transformed[i][j].Y + fit_translate_y) * fit_scale_y;
        }
      for (var i = 0, ilen = cc_transformed.length; i < ilen; i++)
        for (var j = 0, jlen = cc_transformed[i].length; j < jlen; j++) {
          cc_transformed[i][j].X = (cc_transformed[i][j].X + fit_translate_x) * fit_scale_x;
          cc_transformed[i][j].Y = (cc_transformed[i][j].Y + fit_translate_y) * fit_scale_y;
        }
      cpr.Clear();
      cpr.AddPaths(ss_transformed, ClipperLib.PolyType.ptSubject, subj_is_closed);
      cpr.AddPaths(cc_transformed, ClipperLib.PolyType.ptClip, true);
    }
    if (clipType !== "" && offsettable_poly == 3) {
      sss = new ClipperLib.Paths();
      /*
            cpr.PreserveCollinear = false;
            cpr.StrictlySimple = false;
            cpr.ReverseSolution = true;
            //console.log(cpr.PreserveCollinear);
            */
      /*
cpr.ZFillFunction = function (IntPoint_vert1, IntPoint_vert2, IntPoint_intersectPt)
{
  IntPoint_intersectPt.Z = 100;
}      
*/
      if (!subj_is_closed) sss = new ClipperLib.PolyTree();
      var B1 = bench.start("Boolean", "Execute(" + clipType + ", sss, " + subject_fillType + ", " + clip_fillType + ")");
      cpr.Execute(clipType, sss, subject_fillType, clip_fillType);
      bench.end(B1);
      if (!subj_is_closed) sss = ClipperLib.Clipper.PolyTreeToPaths(sss);
      //console.log(JSON.stringify(sss));
    }
  }
  make_offset();
}
  </script>
  <div style="padding-top:3px;padding-left:12px;"><a target="_blank" href="https://sourceforge.net/p/jsclipper/wiki/Donations/" target="_blank">Donate Javascript Clipper Project</a>
  </div>
  <div style="padding-top:3px;padding-left:12px;"><a target="_blank" href="https://sourceforge.net/p/jsclipper/wiki/Main_Demo%206/">Wiki page of this demo</a>&nbsp;&nbsp;&nbsp;More <a target="_blank" href="http://jsclipper.sourceforge.net/6.1.0.1/index.html?p=starter_boolean.html">examples</a>.</div>

<script>
// rev 453
/*******************************************************************************
 *                                                                              *
 * Author    :  Angus Johnson                                                   *
 * Version   :  6.1.3 (float) - Experimental                                    *
 * Date      :  16 January 2014                                                 *
 * Website   :  http://www.angusj.com                                           *
 * Copyright :  Angus Johnson 2010-2014                                         *
 *                                                                              *
 * License:                                                                     *
 * Use, modification & distribution is subject to Boost Software License Ver 1. *
 * http://www.boost.org/LICENSE_1_0.txt                                         *
 *                                                                              *
 * Attributions:                                                                *
 * The code in this library is an extension of Bala Vatti's clipping algorithm: *
 * "A generic solution to polygon clipping"                                     *
 * Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63.             *
 * http://portal.acm.org/citation.cfm?id=129906                                 *
 *                                                                              *
 * Computer graphics and geometric modeling: implementation and algorithms      *
 * By Max K. Agoston                                                            *
 * Springer; 1 edition (January 4, 2005)                                        *
 * https://books.google.com/books?q=vatti+clipping+agoston                       *
 *                                                                              *
 * See also:                                                                    *
 * "Polygon Offsetting by Computing Winding Numbers"                            *
 * Paper no. DETC2005-85513 pp. 565-575                                         *
 * ASME 2005 International Design Engineering Technical Conferences             *
 * and Computers and Information in Engineering Conference (IDETC/CIE2005)      *
 * September 24-28, 2005 , Long Beach, California, USA                          *
 * http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf              *
 *                                                                              *
 *******************************************************************************/
/*******************************************************************************
 *                                                                              *
 * Author    :  Timo                                                            *
 * Version   :  6.1.3.3 (float) - Experimental                                  *
 * Date      :  1 February 2014                                                 *
 *                                                                              *
 * This is a translation of the C# Clipper library to Javascript.               *
 *                                                                              *
 *******************************************************************************/
(function ()
{
  "use strict";

  //use_int32: When enabled 32bit ints are used instead of 64bit ints. This
  //improve performance but coordinate values are limited to the range +/- 46340
  var use_int32 = false;

  //use_xyz: adds a Z member to FPoint. Adds a minor cost to performance.
  var use_xyz = true;

  //UseLines: Enables line clipping. Adds a very minor cost to performance.
  var use_lines = false;

  //use_deprecated: Enables support for the obsolete OffsetPaths() function
  //which has been replace with the ClipperOffset class.
  var use_deprecated = true;

  var ClipperLib = {};
  var isNode = false;
  if (typeof module !== 'undefined' && module.exports)
  {
    module.exports = ClipperLib;
    isNode = true;
  }
  else
  {
    if (typeof (document) !== "undefined") window.ClipperLib = ClipperLib;
    else self['ClipperLib'] = ClipperLib;
  }
  var navigator_appName;
  if (!isNode)
  {
    var nav = navigator.userAgent.toString().toLowerCase();
    navigator_appName = navigator.appName;
  }
  else
  {
    var nav = "chrome"; // Node.js uses Chrome's V8 engine
    navigator_appName = "Netscape"; // Firefox, Chrome and Safari returns "Netscape", so Node.js should also
  }

  var browser = {};
  if (nav.indexOf("chrome") != -1 && nav.indexOf("chromium") == -1) browser.chrome = 1;
  else browser.chrome = 0;
  if (nav.indexOf("chromium") != -1) browser.chromium = 1;
  else browser.chromium = 0;
  if (nav.indexOf("safari") != -1 && nav.indexOf("chrome") == -1 && nav.indexOf("chromium") == -1) browser.safari = 1;
  else browser.safari = 0;
  if (nav.indexOf("firefox") != -1) browser.firefox = 1;
  else browser.firefox = 0;
  if (nav.indexOf("firefox/17") != -1) browser.firefox17 = 1;
  else browser.firefox17 = 0;
  if (nav.indexOf("firefox/15") != -1) browser.firefox15 = 1;
  else browser.firefox15 = 0;
  if (nav.indexOf("firefox/3") != -1) browser.firefox3 = 1;
  else browser.firefox3 = 0;
  if (nav.indexOf("opera") != -1) browser.opera = 1;
  else browser.opera = 0;
  if (nav.indexOf("msie 10") != -1) browser.msie10 = 1;
  else browser.msie10 = 0;
  if (nav.indexOf("msie 9") != -1) browser.msie9 = 1;
  else browser.msie9 = 0;
  if (nav.indexOf("msie 8") != -1) browser.msie8 = 1;
  else browser.msie8 = 0;
  if (nav.indexOf("msie 7") != -1) browser.msie7 = 1;
  else browser.msie7 = 0;
  if (nav.indexOf("msie ") != -1) browser.msie = 1;
  else browser.msie = 0;

  //ClipperLib.biginteger_used = null;

  // Helper function to support Inheritance in Javascript
  if (typeof (Inherit) == 'undefined')
  {
    var Inherit = function (ce, ce2)
    {
      var p;
      if (typeof (Object.getOwnPropertyNames) == 'undefined')
      {
        for (p in ce2.prototype)
          if (typeof (ce.prototype[p]) == 'undefined' || ce.prototype[p] == Object.prototype[p]) ce.prototype[p] = ce2.prototype[p];
        for (p in ce2)
          if (typeof (ce[p]) == 'undefined') ce[p] = ce2[p];
        ce.$baseCtor = ce2;
      }
      else
      {
        var props = Object.getOwnPropertyNames(ce2.prototype);
        for (var i = 0; i < props.length; i++)
          if (typeof (Object.getOwnPropertyDescriptor(ce.prototype, props[i])) == 'undefined') Object.defineProperty(ce.prototype, props[i], Object.getOwnPropertyDescriptor(ce2.prototype, props[i]));
        for (p in ce2)
          if (typeof (ce[p]) == 'undefined') ce[p] = ce2[p];
        ce.$baseCtor = ce2;
      }
    };
  }

  ClipperLib.Path = function ()
  {
    return [];
  };
  ClipperLib.Paths = function ()
  {
    return []; // Was previously [[]], but caused problems when pushed
  };

  // PolyTree & PolyNode start
  // -------------------------------

  ClipperLib.PolyNode = function ()
  {
    this.m_Parent = null;
    this.m_polygon = new ClipperLib.Path();
    this.m_Index = 0;
    this.m_jointype = ClipperLib.JoinType.jtSquare;
    this.m_endtype = ClipperLib.EndType.etClosedPolygon;
    this.m_Childs = []; // new List<PolyNode>()
    this.IsOpen = false;
  };
  ClipperLib.PolyNode.prototype.IsHoleNode = function ()
  {
    var result = true;
    var node = this.m_Parent;
    while (node !== null)
    {
      result = !result;
      node = node.m_Parent;
    }
    return result;
  };
  ClipperLib.PolyNode.prototype.ChildCount = function ()
  {
    return this.m_Childs.length;
  };
  ClipperLib.PolyNode.prototype.Contour = function ()
  {
    return this.m_polygon;
  };
  ClipperLib.PolyNode.prototype.AddChild = function (Child)
  {
    var cnt = this.m_Childs.length;
    this.m_Childs.push(Child);
    Child.m_Parent = this;
    Child.m_Index = cnt;
  };
  ClipperLib.PolyNode.prototype.GetNext = function ()
  {
    if (this.m_Childs.length > 0)
      return this.m_Childs[0];
    else
      return this.GetNextSiblingUp();
  };
  ClipperLib.PolyNode.prototype.GetNextSiblingUp = function ()
  {
    if (this.m_Parent === null)
      return null;
    else if (this.m_Index == this.m_Parent.m_Childs.length - 1)
      return this.m_Parent.GetNextSiblingUp();
    else
      return this.m_Parent.m_Childs[this.m_Index + 1];
  };
  ClipperLib.PolyNode.prototype.Childs = function ()
  {
    return this.m_Childs;
  };
  ClipperLib.PolyNode.prototype.Parent = function ()
  {
    return this.m_Parent;
  };
  ClipperLib.PolyNode.prototype.IsHole = function ()
  {
    return this.IsHoleNode();
  };
  // PolyTree : PolyNode
  ClipperLib.PolyTree = function ()
  {
    this.m_AllPolys = [];
    ClipperLib.PolyNode.call(this);
  };
  ClipperLib.PolyTree.prototype.Clear = function ()
  {
    for (var i = 0, ilen = this.m_AllPolys.length; i < ilen; i++)
      this.m_AllPolys[i] = null;
    this.m_AllPolys.length = 0;
    this.m_Childs.length = 0;
  };
  ClipperLib.PolyTree.prototype.GetFirst = function ()
  {
    if (this.m_Childs.length > 0)
      return this.m_Childs[0];
    else
      return null;
  };
  ClipperLib.PolyTree.prototype.Total = function ()
  {
    return this.m_AllPolys.length;
  };
  Inherit(ClipperLib.PolyTree, ClipperLib.PolyNode);

  // -------------------------------
  // PolyTree & PolyNode end

  /*
  -----------------------------------
  cast_32 speedtest: http://jsperf.com/truncate-float-to-integer/2
  -----------------------------------
  */
  if (browser.msie || browser.opera || browser.safari) ClipperLib.Cast_Int32 = function (a)
  {
    return a | 0;
  };
  else ClipperLib.Cast_Int32 = function (a)
  { // eg. browser.chrome || browser.chromium || browser.firefox
    return~~ a;
  };

  ClipperLib.Clear = function (a)
  {
    a.length = 0;
  };

  ClipperLib.PI = 3.141592653589793;
  ClipperLib.PI2 = 2 * 3.141592653589793;

  ClipperLib.FPoint = function ()
  {
    var a = arguments,
      alen = a.length;
    this.X = 0;
    this.Y = 0;
    if (use_xyz)
    {
      this.Z = 0;
      if (alen == 3) // public FPoint(cInt x, cInt y, cInt z = 0)
      {
        this.X = a[0];
        this.Y = a[1];
        this.Z = a[2];
      }
      else if (alen == 2) // public FPoint(cInt x, cInt y)
      {
        this.X = a[0];
        this.Y = a[1];
        this.Z = 0;
      }
      else if (alen == 1)
      {
        var pt = a[0];
        if (typeof (pt.Z) == "undefined") pt.Z = 0;
        this.X = pt.X;
        this.Y = pt.Y;
        this.Z = pt.Z;
      }
      else // public FPoint()
      {
        this.X = 0;
        this.Y = 0;
        this.Z = 0;
      }
    }
    else // if (!use_xyz)
    {
      if (alen == 2) // public FPoint(cInt X, cInt Y)
      {
        this.X = a[0];
        this.Y = a[1];
      }
      else if (alen == 1)
      {
        var pt = a[0];
        this.X = pt.X;
        this.Y = pt.Y;
      }
      else // public FPoint()
      {
        this.X = 0;
        this.Y = 0;
      }
    }
  };

  ClipperLib.FPoint.op_Equality = function (a, b)
  {
    //return Math.abs(a.X - b.X) < ClipperLib.Globals.tolerance 
    //&& Math.abs(a.Y - b.Y) < ClipperLib.Globals.tolerance;

  return a.X == b.X && a.Y == b.Y;

    // IsAlmostEqual here causes wrong orientations in some cases
    // which seems to be problem only in Javascript Clipper
     return ClipperLib.Global.IsAlmostEqual(a.X, b.X) &&
       ClipperLib.Global.IsAlmostEqual(a.Y, b.Y);
  };
  ClipperLib.FPoint.op_Inequality = function (a, b)
  {
    return a.X != b.X || a.Y != b.Y;

    // IsAlmostEqual here causes wrong orientations in some cases
    // which seems to be problem only in Javascript Clipper
     return !ClipperLib.Global.IsAlmostEqual(a.X, b.X) ||
       !ClipperLib.Global.IsAlmostEqual(a.Y, b.Y);
  };
  /*
  ClipperLib.FPoint.prototype.Equals = function (obj)
  {
    if (obj === null)
        return false;
    if (obj instanceof ClipperLib.FPoint)
    {
        var a = Cast(obj, ClipperLib.FPoint);
        return (this.X == a.X) && (this.Y == a.Y);
    }
    else
        return false;
  };
*/

  if (use_xyz)
  {
    ClipperLib.FPoint0 = function ()
    {
      this.X = 0;
      this.Y = 0;
      this.Z = 0;
    };
    ClipperLib.FPoint1 = function (pt)
    {
      this.X = pt.X;
      this.Y = pt.Y;
      this.Z = pt.Z;
    };
    ClipperLib.FPoint1dp = function (dp)
    {
      this.X = dp.X;
      this.Y = dp.Y;
      this.Z = 0;
    };
    ClipperLib.FPoint2 = function (x, y)
    {
      this.X = x;
      this.Y = y;
      this.Z = 0;
    };
    ClipperLib.FPoint3 = function (x, y, z)
    {
      this.X = x;
      this.Y = y;
      this.Z = z;
    };

  }
  else // if (!use_xyz)
  {
    ClipperLib.FPoint0 = function ()
    {
      this.X = 0;
      this.Y = 0;
    };
    ClipperLib.FPoint1 = function (pt)
    {
      this.X = pt.X;
      this.Y = pt.Y;
    };
    ClipperLib.FPoint1dp = function (dp)
    {
      this.X = dp.X;
      this.Y = dp.Y;
    };
    ClipperLib.FPoint2 = function (x, y)
    {
      this.X = x;
      this.Y = y;
    };
  }

  ClipperLib.FRect = function ()
  {
    var a = arguments,
      alen = a.length;
    if (alen == 4) // function (l, t, r, b)
    {
      this.left = a[0];
      this.top = a[1];
      this.right = a[2];
      this.bottom = a[3];
    }
    else if (alen == 1) // function (ir)
    {
      var ir = a[0];
      this.left = ir.left;
      this.top = ir.top;
      this.right = ir.right;
      this.bottom = ir.bottom;
    }
    else // function ()
    {
      this.left = 0;
      this.top = 0;
      this.right = 0;
      this.bottom = 0;
    }
  };

  ClipperLib.FRect0 = function ()
  {
    this.left = 0;
    this.top = 0;
    this.right = 0;
    this.bottom = 0;
  };
  ClipperLib.FRect1 = function (ir)
  {
    this.left = ir.left;
    this.top = ir.top;
    this.right = ir.right;
    this.bottom = ir.bottom;
  };
  ClipperLib.FRect4 = function (l, t, r, b)
  {
    this.left = l;
    this.top = t;
    this.right = r;
    this.bottom = b;
  };

  ClipperLib.ClipType = {
    ctIntersection: 0,
    ctUnion: 1,
    ctDifference: 2,
    ctXor: 3
  };
  ClipperLib.PolyType = {
    ptSubject: 0,
    ptClip: 1
  };

  //By far the most widely used winding rules for polygon filling are
  //EvenOdd & NonZero (GDI, GDI+, XLib, OpenGL, Cairo, AGG, Quartz, SVG, Gr32)
  //Others rules include Positive, Negative and ABS_GTR_EQ_TWO (only in OpenGL)
  //see http://glprogramming.com/red/chapter11.html
  ClipperLib.PolyFillType = {
    pftEvenOdd: 0,
    pftNonZero: 1,
    pftPositive: 2,
    pftNegative: 3
  };

  ClipperLib.JoinType = {
    jtSquare: 0,
    jtRound: 1,
    jtMiter: 2
  };
  ClipperLib.EndType = {
    etOpenSquare: 0,
    etOpenRound: 1,
    etOpenButt: 2,
    etClosedLine: 3,
    etClosedPolygon: 4
  };
  if (use_deprecated)
    ClipperLib.EndType_ = {
      etSquare: 0,
      etRound: 1,
      etButt: 2,
      etClosed: 3
    };

  ClipperLib.EdgeSide = {
    esLeft: 0,
    esRight: 1
  };
  ClipperLib.Direction = {
    dRightToLeft: 0,
    dLeftToRight: 1
  };

  ClipperLib.TEdge = function ()
  {
    this.Bot = new ClipperLib.FPoint();
    this.Curr = new ClipperLib.FPoint();
    this.Top = new ClipperLib.FPoint();
    this.Delta = new ClipperLib.FPoint();
    this.Dx = 0;
    this.PolyTyp = ClipperLib.PolyType.ptSubject;
    this.Side = ClipperLib.EdgeSide.esLeft;
    this.WindDelta = 0;
    this.WindCnt = 0;
    this.WindCnt2 = 0;
    this.OutIdx = 0;
    this.Next = null;
    this.Prev = null;
    this.NextInLML = null;
    this.NextInAEL = null;
    this.PrevInAEL = null;
    this.NextInSEL = null;
    this.PrevInSEL = null;
  };

  ClipperLib.IntersectNode = function ()
  {
    this.Edge1 = null;
    this.Edge2 = null;
    this.Pt = new ClipperLib.FPoint();

  };

  ClipperLib.MyIntersectNodeSort = function () {};

  ClipperLib.MyIntersectNodeSort.Compare = function (node1, node2)
  {
    var diff = node2.Pt.Y - node1.Pt.Y;
    if (diff == 0) return 0;
    else if (diff > 0) return 1;
    else return -1;
  };

  ClipperLib.LocalMinima = function ()
  {
    this.Y = 0;
    this.LeftBound = null;
    this.RightBound = null;
    this.Next = null;
  };
  ClipperLib.Scanbeam = function ()
  {
    this.Y = 0;
    this.Next = null;
  };
  ClipperLib.OutRec = function ()
  {
    this.Idx = 0;
    this.IsHole = false;
    this.IsOpen = false;
    this.FirstLeft = null; //see comments in clipper.pas
    this.Pts = null;
    this.BottomPt = null;
    this.PolyNode = null;
  };
  ClipperLib.OutPt = function ()
  {
    this.Idx = 0;
    this.Pt = new ClipperLib.FPoint();
    this.Next = null;
    this.Prev = null;
  };
  ClipperLib.Join = function ()
  {
    this.OutPt1 = null;
    this.OutPt2 = null;
    this.OffPt = new ClipperLib.FPoint();
  };

  ClipperLib.Global = function () {};

  // ----------------------------------------------
  // Int64 structure represented as upper and lower 32bit
  var Int64 = function (_lo, _hi)
  {
    this.lo = _lo | 0;
    this.hi = _hi | 0;
  };

  function add(lhs, rhs)
  {
    var a48 = lhs.hi >>> 16;
    var a32 = lhs.hi & 0xFFFF;
    var a16 = lhs.lo >>> 16;
    var a00 = lhs.lo & 0xFFFF;

    var b48 = rhs.hi >>> 16;
    var b32 = rhs.hi & 0xFFFF;
    var b16 = rhs.lo >>> 16;
    var b00 = rhs.lo & 0xFFFF;

    var c48 = 0,
      c32 = 0,
      c16 = 0,
      c00 = 0;
    c00 += a00 + b00;
    c16 += c00 >>> 16;
    c00 &= 0xFFFF;
    c16 += a16 + b16;
    c32 += c16 >>> 16;
    c16 &= 0xFFFF;
    c32 += a32 + b32;
    c48 += c32 >>> 16;
    c32 &= 0xFFFF;
    c48 += a48 + b48;
    c48 &= 0xFFFF;
    return new Int64((c16 << 16) | c00, (c48 << 16) | c32);
  };

  function subtract(lhs, rhs)
  {
    return add(lhs, negate(rhs));
  }

  function greaterthan(a, b)
  {
    var rslt = a.hi - b.hi;
    if (rslt == 0)
      return a.lo > b.lo;
    return (rslt > 0);
  }

  function lessthan(a, b)
  {
    var rslt = a.hi - b.hi;
    if (rslt == 0)
      return a.lo < b.lo;
    return (rslt < 0);
  }

  function lessthanorequal(a, b)
  {
    return !(greaterthan(a, b));
  }

  function negate(val)
  {
    var a = val;
    if (a.hi == Int64_MinValue.hi && a.lo == Int64_MinValue.lo) return Int64_MinValue;
    a.lo = ~a.lo;
    a.hi = ~a.hi;
    ++a.lo;
    if (a.lo === 0)
    ++a.hi;
    return a;
  }

  var maxUlps = new Int64(10000, 0); // 10000
  //var maxUlpsNeg = new Int64(-10000, -1); // -10000
  var buf = new ArrayBuffer(8);
  var aInt = new Int64(0, 0);
  var bInt = new Int64(0, 0);
  var Int64_MinValue = new Int64(0, -2147483648); // -9223372036854775808
  //var Int64_MaxValue = new Int64(-1, 2147483647); // 9223372036854775807
  var zero = new Int64(0, 0);

  function DoubleToInt64Bits(X, xInt)
  {
    var dataView = new DataView(buf);
    dataView.setFloat64(0, X);
    xInt.lo = dataView.getUint32(4) | 0;
    xInt.hi = dataView.getInt32(0) | 0;
  }

  ClipperLib.Global.IsAlmostEqual2 = function (A, B)
  {
    if (A == B) return true;
    DoubleToInt64Bits(A, aInt);
    if (aInt.hi < 0) aInt = subtract(Int64_MinValue, aInt);

    DoubleToInt64Bits(B, bInt);
    if (bInt.hi < 0) bInt = subtract(Int64_MinValue, bInt);

    var sub = subtract(aInt, bInt);
    if (sub.hi < 0) sub = negate(sub);

    if (lessthan(sub, maxUlps)) return true;
    return false;
  }
  // ----------------------------------------------
  ClipperLib.Global.IsAlmostEqual = function (a, b)
  {
    if (a == b) return true;
    var diff = Math.abs(a - b);
    if (diff < 4.94065645841247E-320) return true;
    a = Math.abs(a);
    b = Math.abs(b);
    var smallest = (b < a) ? b : a;
    return diff < smallest * 1e-12;
  }

  ClipperLib.ClipperBase = function ()
  {
    this.m_MinimaList = null;
    this.m_CurrentLM = null;
    this.m_edges = new Array(); // new List<List<TEdge>>()
    this.m_HasOpenPaths = false;
    this.PreserveCollinear = false;
    this.m_MinimaList = null;
    this.m_CurrentLM = null;
    this.m_HasOpenPaths = false;
  };

  // Ranges are in original C# too high for Javascript (in current state 2013 september):
  // protected const double horizontal = -3.4E+38;
  // internal const cInt loRange = 0x3FFFFFFF; // = 1073741823 = sqrt(2^63 -1)/2
  // internal const cInt hiRange = 0x3FFFFFFFFFFFFFFFL; // = 4611686018427387903 = sqrt(2^127 -1)/2
  // So had to adjust them to more suitable for Javascript.

  // If JS some day supports truly 64-bit integers, then these ranges can be as in C#
  // and biginteger library can be more simpler (as then 128bit can be represented as two 64bit numbers)

  //ClipperLib.ClipperBase.horizontal = -9007199254740992; //-2^53
  ClipperLib.ClipperBase.horizontal = -3.4E+38;
  ClipperLib.ClipperBase.Skip = -2;
  ClipperLib.ClipperBase.Unassigned = -1;

  ClipperLib.ClipperBase.IsHorizontal = function (e)
  {
    return e.Delta.Y == 0;
  };
  ClipperLib.ClipperBase.prototype.PointIsVertex = function (pt, pp)
  {
    var pp2 = pp;
    do {
      if (ClipperLib.FPoint.op_Equality(pp2.Pt, pt))
        return true;
      pp2 = pp2.Next;
    }
    while (pp2 != pp)
    return false;
  };

  ClipperLib.ClipperBase.prototype.PointOnLineSegment = function (pt, linePt1, linePt2)
  {
    return ((pt.X == linePt1.X) && (pt.Y == linePt1.Y)) || ((pt.X == linePt2.X) && (pt.Y == linePt2.Y)) || (((pt.X > linePt1.X) == (pt.X < linePt2.X)) && ((pt.Y > linePt1.Y) == (pt.Y < linePt2.Y)) && ((pt.X - linePt1.X) * (linePt2.Y - linePt1.Y) == (linePt2.X - linePt1.X) * (pt.Y - linePt1.Y)));
  /*
  return ((Global.IsAlmostEqual(pt.X, linePt1.X)) && (Global.IsAlmostEqual(pt.Y, linePt1.Y))) ||
    ((Global.IsAlmostEqual(pt.X, linePt2.X)) && (Global.IsAlmostEqual(pt.Y, linePt2.Y))) ||
    (((pt.X > linePt1.X) == (pt.X < linePt2.X)) &&
    ((pt.Y > linePt1.Y) == (pt.Y < linePt2.Y)) &&
    (Global.IsAlmostEqual((pt.X - linePt1.X) * (linePt2.Y - linePt1.Y), 
    (linePt2.X - linePt1.X) * (pt.Y - linePt1.Y))));
  */
  };

  ClipperLib.ClipperBase.prototype.PointOnPolygon = function (pt, pp)
  {
    var pp2 = pp;
    while (true)
    {
      if (this.PointOnLineSegment(pt, pp2.Pt, pp2.Next.Pt))
        return true;
      pp2 = pp2.Next;
      if (pp2 == pp)
        break;
    }
    return false;
  };

  ClipperLib.ClipperBase.prototype.SlopesEqual = ClipperLib.ClipperBase.SlopesEqual = function ()
  {
    var a = arguments,
      alen = a.length;
    if (alen == 2) // ClipperLib.ClipperBase.SlopesEqual = function (e1, e2)
    {
      var e1 = a[0],
        e2 = a[1];
      return ClipperLib.Global.IsAlmostEqual(e1.Delta.Y * e2.Delta.X, e1.Delta.X * e2.Delta.Y);
    }
    else if (alen == 3) // ClipperLib.ClipperBase.SlopesEqual = function (pt1, pt2, pt3)
    {
      var pt1 = a[0],
        pt2 = a[1],
        pt3 = a[2];
      return ClipperLib.Global.IsAlmostEqual((pt1.Y - pt2.Y) * (pt2.X - pt3.X), (pt1.X - pt2.X) * (pt2.Y - pt3.Y));
    }
    else if (alen == 4)
    {
      var pt1 = a[0],
        pt2 = a[1],
        pt3 = a[2],
        pt4 = a[3];
      return ClipperLib.Global.IsAlmostEqual((pt1.Y - pt2.Y) * (pt3.X - pt4.X), (pt1.X - pt2.X) * (pt3.Y - pt4.Y));
    };
  };

  ClipperLib.ClipperBase.prototype.Clear = function ()
  {
    this.DisposeLocalMinimaList();
    for (var i = 0, ilen = this.m_edges.length; i < ilen; ++i)
    {
      for (var j = 0, jlen = this.m_edges[i].length; j < jlen; ++j) this.m_edges[i][j] = null;
      ClipperLib.Clear(this.m_edges[i]);
    }
    ClipperLib.Clear(this.m_edges);
    this.m_HasOpenPaths = false;
  };
  ClipperLib.ClipperBase.prototype.DisposeLocalMinimaList = function ()
  {
    while (this.m_MinimaList !== null)
    {
      var tmpLm = this.m_MinimaList.Next;
      this.m_MinimaList = null;
      this.m_MinimaList = tmpLm;
    }
    this.m_CurrentLM = null;
  };

  ClipperLib.ClipperBase.prototype.InitEdge = function (e, eNext, ePrev, pt)
  {
    e.Next = eNext;
    e.Prev = ePrev;
    //e.Curr = pt;
    e.Curr.X = pt.X;
    e.Curr.Y = pt.Y;
    e.OutIdx = ClipperLib.ClipperBase.Unassigned;
  };

  ClipperLib.ClipperBase.prototype.InitEdge2 = function (e, polyType)
  {
    if (e.Curr.Y >= e.Next.Curr.Y)
    {
      //e.Bot = e.Curr;
      e.Bot.X = e.Curr.X;
      e.Bot.Y = e.Curr.Y;

      //e.Top = e.Next.Curr;
      e.Top.X = e.Next.Curr.X;
      e.Top.Y = e.Next.Curr.Y;
    }
    else
    {
      //e.Top = e.Curr;
      e.Top.X = e.Curr.X;
      e.Top.Y = e.Curr.Y;

      //e.Bot = e.Next.Curr;
      e.Bot.X = e.Next.Curr.X;
      e.Bot.Y = e.Next.Curr.Y;
    }
    this.SetDx(e);
    e.PolyTyp = polyType;
  };

  ClipperLib.ClipperBase.prototype.FindNextLocMin = function (E)
  {
    var E2;
    for (;;)
    {
      while (ClipperLib.FPoint.op_Inequality(E.Bot, E.Prev.Bot) || ClipperLib.FPoint.op_Equality(E.Curr, E.Top))
        E = E.Next;
      if (E.Dx != ClipperLib.ClipperBase.horizontal && E.Prev.Dx != ClipperLib.ClipperBase.horizontal)
        break;
      while (E.Prev.Dx == ClipperLib.ClipperBase.horizontal)
        E = E.Prev;
      E2 = E;
      while (E.Dx == ClipperLib.ClipperBase.horizontal)
        E = E.Next;
      if (E.Top.Y == E.Prev.Bot.Y)
        continue;
      //ie just an intermediate horz.
      if (E2.Prev.Bot.X < E.Bot.X)
        E = E2;
      break;
    }
    return E;
  };
  ClipperLib.ClipperBase.prototype.ProcessBound = function (E, IsClockwise)
  {
    var EStart = E,
      Result = E;
    var Horz;
    var StartX;
    if (E.Dx == ClipperLib.ClipperBase.horizontal)
    {
      //it's possible for adjacent overlapping horz edges to start heading left
      //before finishing right, so ...
      if (IsClockwise)
        StartX = E.Prev.Bot.X;
      else
        StartX = E.Next.Bot.X;
      if (E.Bot.X != StartX)
        this.ReverseHorizontal(E);
    }
    if (Result.OutIdx != ClipperLib.ClipperBase.Skip)
    {
      if (IsClockwise)
      {
        while (Result.Top.Y == Result.Next.Bot.Y && Result.Next.OutIdx != ClipperLib.ClipperBase.Skip)
          Result = Result.Next;
        if (Result.Dx == ClipperLib.ClipperBase.horizontal && Result.Next.OutIdx != ClipperLib.ClipperBase.Skip)
        {
          //nb: at the top of a bound, horizontals are added to the bound
          //only when the preceding edge attaches to the horizontal's left vertex
          //unless a Skip edge is encountered when that becomes the top divide
          Horz = Result;
          while (Horz.Prev.Dx == ClipperLib.ClipperBase.horizontal)
            Horz = Horz.Prev;
          if (Horz.Prev.Top.X == Result.Next.Top.X)
          {
            if (!IsClockwise)
              Result = Horz.Prev;
          }
          else if (Horz.Prev.Top.X > Result.Next.Top.X)
            Result = Horz.Prev;
        }
        while (E != Result)
        {
          E.NextInLML = E.Next;
          if (E.Dx == ClipperLib.ClipperBase.horizontal && E != EStart && E.Bot.X != E.Prev.Top.X)
            this.ReverseHorizontal(E);
          E = E.Next;
        }

        if (E.Dx == ClipperLib.ClipperBase.horizontal && E != EStart && E.Bot.X != E.Prev.Top.X)
          this.ReverseHorizontal(E);
        Result = Result.Next;
        //move to the edge just beyond current bound
      }
      else
      {
        while (Result.Top.Y == Result.Prev.Bot.Y && Result.Prev.OutIdx != ClipperLib.ClipperBase.Skip)
          Result = Result.Prev;
        if (Result.Dx == ClipperLib.ClipperBase.horizontal && Result.Prev.OutIdx != ClipperLib.ClipperBase.Skip)
        {
          Horz = Result;
          while (Horz.Next.Dx == ClipperLib.ClipperBase.horizontal)
            Horz = Horz.Next;
          if (Horz.Next.Top.X == Result.Prev.Top.X)
          {
            if (!IsClockwise)
              Result = Horz.Next;
          }
          else if (Horz.Next.Top.X > Result.Prev.Top.X)
            Result = Horz.Next;
        }
        while (E != Result)
        {
          E.NextInLML = E.Prev;
          if (E.Dx == ClipperLib.ClipperBase.horizontal && E != EStart && E.Bot.X != E.Next.Top.X)
            this.ReverseHorizontal(E);
          E = E.Prev;
        }
        if (E.Dx == ClipperLib.ClipperBase.horizontal && E != EStart && E.Bot.X != E.Next.Top.X)
          this.ReverseHorizontal(E);
        Result = Result.Prev;
        //move to the edge just beyond current bound
      }
    }
    if (Result.OutIdx == ClipperLib.ClipperBase.Skip)
    {
      //if edges still remain in the current bound beyond the skip edge then
      //create another LocMin and call ProcessBound once more
      E = Result;
      if (IsClockwise)
      {
        while (E.Top.Y == E.Next.Bot.Y)
          E = E.Next;
        //don't include top horizontals when parsing a bound a second time,
        //they will be contained in the opposite bound ...
        while (E != Result && E.Dx == ClipperLib.ClipperBase.horizontal)
          E = E.Prev;
      }
      else
      {
        while (E.Top.Y == E.Prev.Bot.Y)
          E = E.Prev;
        while (E != Result && E.Dx == ClipperLib.ClipperBase.horizontal)
          E = E.Next;
      }
      if (E == Result)
      {
        if (IsClockwise)
          Result = E.Next;
        else
          Result = E.Prev;
      }
      else
      {
        //there are more edges in the bound beyond result starting with E
        if (IsClockwise)
          E = Result.Next;
        else
          E = Result.Prev;
        var locMin = new ClipperLib.LocalMinima();
        locMin.Next = null;
        locMin.Y = E.Bot.Y;
        locMin.LeftBound = null;
        locMin.RightBound = E;
        locMin.RightBound.WindDelta = 0;
        Result = this.ProcessBound(locMin.RightBound, IsClockwise);
        this.InsertLocalMinima(locMin);
      }
    }
    return Result;
  };

  ClipperLib.ClipperBase.prototype.AddPath = function (pg, polyType, Closed)
  {
    if (use_lines)
    {
      if (!Closed && polyType == ClipperLib.PolyType.ptClip)
        ClipperLib.Error("AddPath: Open paths must be subject.");
    }
    else
    {
      if (!Closed)
        ClipperLib.Error("AddPath: Open paths have been disabled.");
    }

    var highI = pg.length - 1;
    if (Closed)
      while (highI > 0 && (ClipperLib.FPoint.op_Equality(pg[highI], pg[0])))
    --highI;
    while (highI > 0 && (ClipperLib.FPoint.op_Equality(pg[highI], pg[highI - 1])))
    --highI;
    if ((Closed && highI < 2) || (!Closed && highI < 1))
      return false;
    //create a new edge array ...
    var edges = new Array();
    for (var i = 0; i <= highI; i++)
      edges.push(new ClipperLib.TEdge());
    var IsFlat = true;
    //1. Basic (first) edge initialization ...

    //edges[1].Curr = pg[1];
    edges[1].Curr.X = pg[1].X;
    edges[1].Curr.Y = pg[1].Y;

    this.InitEdge(edges[0], edges[1], edges[highI], pg[0]);
    this.InitEdge(edges[highI], edges[0], edges[highI - 1], pg[highI]);
    for (var i = highI - 1; i >= 1; --i)
    {
      this.InitEdge(edges[i], edges[i + 1], edges[i - 1], pg[i]);
    }
    var eStart = edges[0];

    //2. Remove duplicate vertices, and (when closed) collinear edges ...
    var E = eStart,
      eLoopStop = eStart;
    for (;;)
    {
      if (ClipperLib.FPoint.op_Equality(E.Curr, E.Next.Curr))
      {
        if (E == E.Next)
          break;
        if (E == eStart)
          eStart = E.Next;
        E = this.RemoveEdge(E);
        eLoopStop = E;
        continue;
      }
      if (E.Prev == E.Next)
        break;
      else if (Closed && ClipperLib.ClipperBase.SlopesEqual(E.Prev.Curr, E.Curr, E.Next.Curr) && (!this.PreserveCollinear || !this.Pt2IsBetweenPt1AndPt3(E.Prev.Curr, E.Curr, E.Next.Curr)))
      {
        //Collinear edges are allowed for open paths but in closed paths
        //the default is to merge adjacent collinear edges into a single edge.
        //However, if the PreserveCollinear property is enabled, only overlapping
        //collinear edges (ie spikes) will be removed from closed paths.
        if (E == eStart)
          eStart = E.Next;
        E = this.RemoveEdge(E);
        E = E.Prev;
        eLoopStop = E;
        continue;
      }
      E = E.Next;
      if (E == eLoopStop)
        break;
    }
    if ((!Closed && (E == E.Next)) || (Closed && (E.Prev == E.Next)))
      return false;
    if (!Closed)
    {
      this.m_HasOpenPaths = true;
      eStart.Prev.OutIdx = ClipperLib.ClipperBase.Skip;
    }
    //3. Do second stage of edge initialization ...
    var eHighest = eStart;
    E = eStart;
    do {
      this.InitEdge2(E, polyType);
      E = E.Next;
      if (IsFlat && E.Curr.Y != eStart.Curr.Y)
        IsFlat = false;
    }
    while (E != eStart)
    //4. Finally, add edge bounds to LocalMinima list ...
    //Totally flat paths must be handled differently when adding them
    //to LocalMinima list to avoid endless loops etc ...
    if (IsFlat)
    {
      if (Closed)
        return false;
      E.Prev.OutIdx = ClipperLib.ClipperBase.Skip;
      if (E.Prev.Bot.X < E.Prev.Top.X)
        this.ReverseHorizontal(E.Prev);
      var locMin = new ClipperLib.LocalMinima();
      locMin.Next = null;
      locMin.Y = E.Bot.Y;
      locMin.LeftBound = null;
      locMin.RightBound = E;
      locMin.RightBound.Side = ClipperLib.EdgeSide.esRight;
      locMin.RightBound.WindDelta = 0;
      while (E.Next.OutIdx != ClipperLib.ClipperBase.Skip)
      {
        E.NextInLML = E.Next;
        if (E.Bot.X != E.Prev.Top.X)
          this.ReverseHorizontal(E);
        E = E.Next;
      }
      this.InsertLocalMinima(locMin);
      this.m_edges.push(edges);
      return true;

    }
    this.m_edges.push(edges);

    var clockwise;
    var EMin = null;
    for (;;)
    {
      E = this.FindNextLocMin(E);
      if (E == EMin)
        break;
      else if (EMin == null)
        EMin = E;
      //E and E.Prev now share a local minima (left aligned if horizontal).
      //Compare their slopes to find which starts which bound ...
      var locMin = new ClipperLib.LocalMinima();
      locMin.Next = null;
      locMin.Y = E.Bot.Y;
      if (E.Dx < E.Prev.Dx)
      {
        locMin.LeftBound = E.Prev;
        locMin.RightBound = E;
        clockwise = false;
        //Q.nextInLML = Q.prev
      }
      else
      {
        locMin.LeftBound = E;
        locMin.RightBound = E.Prev;
        clockwise = true;
        //Q.nextInLML = Q.next
      }
      locMin.LeftBound.Side = ClipperLib.EdgeSide.esLeft;
      locMin.RightBound.Side = ClipperLib.EdgeSide.esRight;
      if (!Closed)
        locMin.LeftBound.WindDelta = 0;
      else if (locMin.LeftBound.Next == locMin.RightBound)
        locMin.LeftBound.WindDelta = -1;
      else
        locMin.LeftBound.WindDelta = 1;
      locMin.RightBound.WindDelta = -locMin.LeftBound.WindDelta;
      E = this.ProcessBound(locMin.LeftBound, clockwise);
      var E2 = this.ProcessBound(locMin.RightBound, !clockwise);
      if (locMin.LeftBound.OutIdx == ClipperLib.ClipperBase.Skip)
        locMin.LeftBound = null;
      else if (locMin.RightBound.OutIdx == ClipperLib.ClipperBase.Skip)
        locMin.RightBound = null;
      this.InsertLocalMinima(locMin);
      if (!clockwise)
        E = E2;
    }
    return true;
  };

  ClipperLib.ClipperBase.prototype.AddPaths = function (ppg, polyType, closed)
  {
    var result = false;
    for (var i = 0, ilen = ppg.length; i < ilen; ++i)
      if (this.AddPath(ppg[i], polyType, closed))
        result = true;
    return result;
  };
  //------------------------------------------------------------------------------


  ClipperLib.ClipperBase.prototype.Pt2IsBetweenPt1AndPt3 = function (pt1, pt2, pt3)
  {
    if ((ClipperLib.FPoint.op_Equality(pt1, pt3)) || (ClipperLib.FPoint.op_Equality(pt1, pt2)) ||
      (ClipperLib.FPoint.op_Equality(pt3, pt2)))
      return false;
    else if (pt1.X != pt3.X)
      return (pt2.X > pt1.X) == (pt2.X < pt3.X);
    else
      return (pt2.Y > pt1.Y) == (pt2.Y < pt3.Y);
  };

  ClipperLib.ClipperBase.prototype.RemoveEdge = function (e)
  {
    //removes e from double_linked_list (but without removing from memory)
    e.Prev.Next = e.Next;
    e.Next.Prev = e.Prev;
    var result = e.Next;
    e.Prev = null; //flag as removed (see ClipperBase.Clear)
    return result;
  };

  ClipperLib.ClipperBase.prototype.SetDx = function (e)
  {
    e.Delta.X = (e.Top.X - e.Bot.X);
    e.Delta.Y = (e.Top.Y - e.Bot.Y);
    if (e.Delta.Y == 0) e.Dx = ClipperLib.ClipperBase.horizontal;
    else e.Dx = (e.Delta.X) / (e.Delta.Y);
  };

  ClipperLib.ClipperBase.prototype.InsertLocalMinima = function (newLm)
  {
    if (this.m_MinimaList === null)
    {
      this.m_MinimaList = newLm;
    }
    else if (newLm.Y >= this.m_MinimaList.Y)
    {
      newLm.Next = this.m_MinimaList;
      this.m_MinimaList = newLm;
    }
    else
    {
      var tmpLm = this.m_MinimaList;
      while (tmpLm.Next !== null && (newLm.Y < tmpLm.Next.Y))
        tmpLm = tmpLm.Next;
      newLm.Next = tmpLm.Next;
      tmpLm.Next = newLm;
    }
  };

  ClipperLib.ClipperBase.prototype.PopLocalMinima = function ()
  {
    if (this.m_CurrentLM === null)
      return;
    this.m_CurrentLM = this.m_CurrentLM.Next;
  };

  ClipperLib.ClipperBase.prototype.ReverseHorizontal = function (e)
  {
    //swap horizontal edges' top and bottom x's so they follow the natural
    //progression of the bounds - ie so their xbots will align with the
    //adjoining lower edge. [Helpful in the ProcessHorizontal() method.]
    var tmp = e.Top.X;
    e.Top.X = e.Bot.X;
    e.Bot.X = tmp;
    if (use_xyz)
    {
      var tmp2 = e.Top.Z;
      e.Top.Z = e.Bot.Z;
      e.Bot.Z = tmp2;
    }
  };

  ClipperLib.ClipperBase.prototype.Reset = function ()
  {
    this.m_CurrentLM = this.m_MinimaList;
    if (this.m_CurrentLM == null)
      return;
    //ie nothing to process
    //reset all edges ...
    var lm = this.m_MinimaList;
    while (lm != null)
    {
      var e = lm.LeftBound;
      if (e != null)
      {
        //e.Curr = e.Bot;
        e.Curr.X = e.Bot.X;
        e.Curr.Y = e.Bot.Y;

        e.Side = ClipperLib.EdgeSide.esLeft;
        e.OutIdx = ClipperLib.ClipperBase.Unassigned;
      }
      e = lm.RightBound;
      if (e != null)
      {
        //e.Curr = e.Bot;
        e.Curr.X = e.Bot.X;
        e.Curr.Y = e.Bot.Y;

        e.Side = ClipperLib.EdgeSide.esRight;
        e.OutIdx = ClipperLib.ClipperBase.Unassigned;
      }
      lm = lm.Next;
    }
  };

  ClipperLib.ClipperBase.GetBounds = function (paths)
  {
    var i = 0,
      cnt = paths.length;
    while (i < cnt && paths[i].length == 0)
      i++;
    if (i == cnt)
      return new ClipperLib.FRect(0, 0, 0, 0);
    var result = new ClipperLib.FRect();
    result.left = paths[i][0].X;
    result.right = result.left;
    result.top = paths[i][0].Y;
    result.bottom = result.top;
    for (; i < cnt; i++)
      for (var j = 0; j < paths[i].length; j++)
      {
        if (paths[i][j].X < result.left)
          result.left = paths[i][j].X;
        else if (paths[i][j].X > result.right)
          result.right = paths[i][j].X;
        if (paths[i][j].Y < result.top)
          result.top = paths[i][j].Y;
        else if (paths[i][j].Y > result.bottom)
          result.bottom = paths[i][j].Y;
      }
    return result;
  };

  ClipperLib.ClipperBase.prototype.GetBounds2 = function ()
  {
    var result = new ClipperLib.FRect();
    var lm = this.m_MinimaList;
    if (lm === null)
      return result;
    result.left = lm.LeftBound.Bot.X;
    result.top = lm.LeftBound.Bot.Y;
    result.right = lm.LeftBound.Bot.X;
    result.bottom = lm.LeftBound.Bot.Y;
    while (lm !== null)
    {
      if (lm.LeftBound.Bot.Y > result.bottom)
        result.bottom = lm.LeftBound.Bot.Y;
      var e = lm.LeftBound;
      for (;;)
      {
        var bottomE = e;
        while (e.NextInLML !== null)
        {
          if (e.Bot.X < result.left)
            result.left = e.Bot.X;
          if (e.Bot.X > result.right)
            result.right = e.Bot.X;
          e = e.NextInLML;
        }
        if (e.Bot.X < result.left)
          result.left = e.Bot.X;
        if (e.Bot.X > result.right)
          result.right = e.Bot.X;
        if (e.Top.X < result.left)
          result.left = e.Top.X;
        if (e.Top.X > result.right)
          result.right = e.Top.X;
        if (e.Top.Y < result.top)
          result.top = e.Top.Y;
        if (bottomE == lm.LeftBound)
          e = lm.RightBound;
        else
          break;
      }
      lm = lm.Next;
    }
    return result;
  };

  ClipperLib.Clipper = function (InitOptions) // public Clipper(int InitOptions = 0)
  {
    if (typeof (InitOptions) == "undefined") InitOptions = 0;
    this.m_PolyOuts = null;
    this.m_ClipType = ClipperLib.ClipType.ctIntersection;
    this.m_Scanbeam = null;
    this.m_ActiveEdges = null;
    this.m_SortedEdges = null;
    this.m_IntersectList = null;
    this.m_IntersectNodeComparer = null;
    this.m_ExecuteLocked = false;
    this.m_ClipFillType = ClipperLib.PolyFillType.pftEvenOdd;
    this.m_SubjFillType = ClipperLib.PolyFillType.pftEvenOdd;
    this.m_Joins = null;
    this.m_GhostJoins = null;
    this.m_UsingPolyTree = false;
    this.ReverseSolution = false;
    this.StrictlySimple = false;
    ClipperLib.ClipperBase.call(this);
    this.m_Scanbeam = null;
    this.m_ActiveEdges = null;
    this.m_SortedEdges = null;
    this.m_IntersectList = new Array();
    this.m_IntersectNodeComparer = ClipperLib.MyIntersectNodeSort.Compare;
    this.m_ExecuteLocked = false;
    this.m_UsingPolyTree = false;
    this.m_PolyOuts = new Array();
    this.m_Joins = new Array();
    this.m_GhostJoins = new Array();
    this.ReverseSolution = (ClipperLib.Clipper.ioReverseSolution & InitOptions) != 0;
    this.StrictlySimple = (ClipperLib.Clipper.ioStrictlySimple & InitOptions) != 0;
    this.PreserveCollinear = (ClipperLib.Clipper.ioPreserveCollinear & InitOptions) != 0;
    if (use_xyz)
    {
      this.ZFillFunction = null; // function (IntPoint vert1, IntPoint vert2, ref IntPoint intersectPt);
    }
  };

  ClipperLib.Clipper.ioReverseSolution = 1;
  ClipperLib.Clipper.ioStrictlySimple = 2;
  ClipperLib.Clipper.ioPreserveCollinear = 4;

  ClipperLib.Clipper.prototype.Clear = function ()
  {
    if (this.m_edges.length === 0)
      return;
    //avoids problems with ClipperBase destructor
    this.DisposeAllPolyPts();
    ClipperLib.ClipperBase.prototype.Clear.call(this);
  };

  ClipperLib.Clipper.prototype.DisposeScanbeamList = function ()
  {
    while (this.m_Scanbeam !== null)
    {
      var sb2 = this.m_Scanbeam.Next;
      this.m_Scanbeam = null;
      this.m_Scanbeam = sb2;
    }
  };

  ClipperLib.Clipper.prototype.Reset = function ()
  {
    ClipperLib.ClipperBase.prototype.Reset.call(this);
    this.m_Scanbeam = null;
    this.m_ActiveEdges = null;
    this.m_SortedEdges = null;
    this.DisposeAllPolyPts();
    var lm = this.m_MinimaList;
    while (lm !== null)
    {
      this.InsertScanbeam(lm.Y);
      lm = lm.Next;
    }
  };

  ClipperLib.Clipper.prototype.InsertScanbeam = function (Y)
  {
    if (this.m_Scanbeam === null)
    {
      this.m_Scanbeam = new ClipperLib.Scanbeam();
      this.m_Scanbeam.Next = null;
      this.m_Scanbeam.Y = Y;
    }
    else if (Y > this.m_Scanbeam.Y)
    {
      var newSb = new ClipperLib.Scanbeam();
      newSb.Y = Y;
      newSb.Next = this.m_Scanbeam;
      this.m_Scanbeam = newSb;
    }
    else
    {
      var sb2 = this.m_Scanbeam;
      while (sb2.Next !== null && (Y <= sb2.Next.Y))
        sb2 = sb2.Next;
      if (Y == sb2.Y)
        return;
      //ie ignores duplicates
      var newSb = new ClipperLib.Scanbeam();
      newSb.Y = Y;
      newSb.Next = sb2.Next;
      sb2.Next = newSb;
    }
  };

  // ************************************

  ClipperLib.Clipper.prototype.Execute = function ()
  {
    var a = arguments,
      alen = a.length,
      ispolytree = a[1] instanceof ClipperLib.PolyTree;
    if (alen == 4 && !ispolytree) // function (clipType, solution, subjFillType, clipFillType)
    {
      var clipType = a[0],
        solution = a[1],
        subjFillType = a[2],
        clipFillType = a[3];
      if (this.m_ExecuteLocked)
        return false;
      if (this.m_HasOpenPaths)
        ClipperLib.Error("Error: PolyTree struct is need for open path clipping.");
      this.m_ExecuteLocked = true;
      ClipperLib.Clear(solution);
      this.m_SubjFillType = subjFillType;
      this.m_ClipFillType = clipFillType;
      this.m_ClipType = clipType;
      this.m_UsingPolyTree = false;
      var succeeded = this.ExecuteInternal();
      //build the return polygons ...
      if (succeeded)
        this.BuildResult(solution);
      this.m_ExecuteLocked = false;
      return succeeded;
    }
    else if (alen == 4 && ispolytree) // function (clipType, polytree, subjFillType, clipFillType)
    {
      var clipType = a[0],
        polytree = a[1],
        subjFillType = a[2],
        clipFillType = a[3];
      if (this.m_ExecuteLocked)
        return false;
      this.m_ExecuteLocked = true;
      this.m_SubjFillType = subjFillType;
      this.m_ClipFillType = clipFillType;
      this.m_ClipType = clipType;
      this.m_UsingPolyTree = true;
      var succeeded = this.ExecuteInternal();
      //build the return polygons ...
      if (succeeded)
        this.BuildResult2(polytree);
      this.m_ExecuteLocked = false;
      return succeeded;
    }
    else if (alen == 2 && !ispolytree) // function (clipType, solution)
    {
      var clipType = a[0],
        solution = a[1];
      return this.Execute(clipType, solution, ClipperLib.PolyFillType.pftEvenOdd, ClipperLib.PolyFillType.pftEvenOdd);
    }
    else if (alen == 2 && ispolytree) // function (clipType, polytree)
    {
      var clipType = a[0],
        polytree = a[1];
      return this.Execute(clipType, polytree, ClipperLib.PolyFillType.pftEvenOdd, ClipperLib.PolyFillType.pftEvenOdd);
    }
  };

  ClipperLib.Clipper.prototype.FixHoleLinkage = function (outRec)
  {
    //skip if an outermost polygon or
    //already already points to the correct FirstLeft ...
    if (outRec.FirstLeft === null ||
      (outRec.IsHole != outRec.FirstLeft.IsHole &&
        outRec.FirstLeft.Pts !== null)) return;

    var orfl = outRec.FirstLeft;
    while (orfl !== null && ((orfl.IsHole == outRec.IsHole) || orfl.Pts === null))
      orfl = orfl.FirstLeft;
    outRec.FirstLeft = orfl;
  };

  ClipperLib.Clipper.prototype.ExecuteInternal = function ()
  {
    try
    {
      this.Reset();
      if (this.m_CurrentLM === null)
        return false;
      var botY = this.PopScanbeam();
      do {
        this.InsertLocalMinimaIntoAEL(botY);
        ClipperLib.Clear(this.m_GhostJoins);
        this.ProcessHorizontals(false);
        if (this.m_Scanbeam === null)
          break;
        var topY = this.PopScanbeam();
        if (!this.ProcessIntersections(botY, topY))
          return false;
        this.ProcessEdgesAtTopOfScanbeam(topY);
        botY = topY;
      }
      while (this.m_Scanbeam !== null || this.m_CurrentLM !== null)

      //fix orientations ...
      for (var i = 0, ilen = this.m_PolyOuts.length; i < ilen; i++)
      {
        var outRec = this.m_PolyOuts[i];
        if (outRec.Pts === null || outRec.IsOpen)
          continue;
        if ((outRec.IsHole ^ this.ReverseSolution) == (this.Area(outRec) > 0))
          this.ReversePolyPtLinks(outRec.Pts);
      }
      this.JoinCommonEdges();
      for (var i = 0, ilen = this.m_PolyOuts.length; i < ilen; i++)
      {
        var outRec = this.m_PolyOuts[i];
        if (outRec.Pts !== null && !outRec.IsOpen)
          this.FixupOutPolygon(outRec);
      }
      if (this.StrictlySimple)
        this.DoSimplePolygons();
      return true;
    }
    finally
    {
      ClipperLib.Clear(this.m_Joins);
      ClipperLib.Clear(this.m_GhostJoins);
    }
  };

  ClipperLib.Clipper.prototype.PopScanbeam = function ()
  {
    var Y = this.m_Scanbeam.Y;
    var sb2 = this.m_Scanbeam;
    this.m_Scanbeam = this.m_Scanbeam.Next;
    sb2 = null;
    return Y;
  };

  ClipperLib.Clipper.prototype.DisposeAllPolyPts = function ()
  {
    for (var i = 0, ilen = this.m_PolyOuts.length; i < ilen; ++i)
      this.DisposeOutRec(i);
    ClipperLib.Clear(this.m_PolyOuts);
  };

  ClipperLib.Clipper.prototype.DisposeOutRec = function (index)
  {
    var outRec = this.m_PolyOuts[index];
    if (outRec.Pts !== null)
      this.DisposeOutPts(outRec.Pts);
    outRec = null;
    this.m_PolyOuts[index] = null;
  };

  ClipperLib.Clipper.prototype.DisposeOutPts = function (pp)
  {
    if (pp === null)
      return;
    var tmpPp = null;
    pp.Prev.Next = null;
    while (pp !== null)
    {
      tmpPp = pp;
      pp = pp.Next;
      tmpPp = null;
    }
  };

  ClipperLib.Clipper.prototype.AddJoin = function (Op1, Op2, OffPt)
  {
    var j = new ClipperLib.Join();
    j.OutPt1 = Op1;
    j.OutPt2 = Op2;
    //j.OffPt = OffPt;
    j.OffPt.X = OffPt.X;
    j.OffPt.Y = OffPt.Y;

    this.m_Joins.push(j);
  };

  ClipperLib.Clipper.prototype.AddGhostJoin = function (Op, OffPt)
  {
    var j = new ClipperLib.Join();
    j.OutPt1 = Op;
    //j.OffPt = OffPt;
    j.OffPt.X = OffPt.X;
    j.OffPt.Y = OffPt.Y;

    this.m_GhostJoins.push(j);
  };

  if (use_xyz)
  {
    ClipperLib.Clipper.prototype.SetZ = function (pt, e)
    {
      pt.Z = 0;
      if (this.ZFillFunction !== null)
      {
        //put the 'preferred' point as first parameter ...
        if (e.OutIdx < 0)
          this.ZFillFunction(e.Bot, e.Top, pt); //outside a path so presume entering
        else
          this.ZFillFunction(e.Top, e.Bot, pt); //inside a path so presume exiting
      }
    };
    //------------------------------------------------------------------------------
  }

  ClipperLib.Clipper.prototype.InsertLocalMinimaIntoAEL = function (botY)
  {
    while (this.m_CurrentLM !== null && (this.m_CurrentLM.Y == botY))
    {
      var lb = this.m_CurrentLM.LeftBound;
      var rb = this.m_CurrentLM.RightBound;
      this.PopLocalMinima();
      var Op1 = null;
      if (lb === null)
      {
        this.InsertEdgeIntoAEL(rb, null);
        this.SetWindingCount(rb);
        if (this.IsContributing(rb))
          Op1 = this.AddOutPt(rb, rb.Bot);
      }
      else if (rb === null)
      {
        this.InsertEdgeIntoAEL(lb, null);
        this.SetWindingCount(lb);
        if (this.IsContributing(lb))
          Op1 = this.AddOutPt(lb, lb.Bot);
        this.InsertScanbeam(lb.Top.Y);
      }
      else
      {
        this.InsertEdgeIntoAEL(lb, null);
        this.InsertEdgeIntoAEL(rb, lb);
        this.SetWindingCount(lb);
        rb.WindCnt = lb.WindCnt;
        rb.WindCnt2 = lb.WindCnt2;
        if (this.IsContributing(lb))
          Op1 = this.AddLocalMinPoly(lb, rb, lb.Bot);
        this.InsertScanbeam(lb.Top.Y);
      }
      if (rb != null)
      {
        if (ClipperLib.ClipperBase.IsHorizontal(rb))
          this.AddEdgeToSEL(rb);
        else
          this.InsertScanbeam(rb.Top.Y);
      }
      if (lb == null || rb == null) continue;

      //if output polygons share an Edge with a horizontal rb, they'll need joining later ...
      if (Op1 !== null && ClipperLib.ClipperBase.IsHorizontal(rb) && this.m_GhostJoins.length > 0 && rb.WindDelta !== 0)
      {
        for (var i = 0, ilen = this.m_GhostJoins.length; i < ilen; i++)
        {
          //if the horizontal Rb and a 'ghost' horizontal overlap, then convert
          //the 'ghost' join to a real join ready for later ...
          var j = this.m_GhostJoins[i];
          if (this.HorzSegmentsOverlap(j.OutPt1.Pt, j.OffPt, rb.Bot, rb.Top))
            this.AddJoin(j.OutPt1, Op1, j.OffPt);
        }
      }
      if (lb.OutIdx >= 0 && lb.PrevInAEL !== null &&
        lb.PrevInAEL.Curr.X == lb.Bot.X &&
        lb.PrevInAEL.OutIdx >= 0 &&
        ClipperLib.ClipperBase.SlopesEqual(lb.PrevInAEL, lb) &&
        lb.WindDelta !== 0 && lb.PrevInAEL.WindDelta !== 0)
      {
        var Op2 = this.AddOutPt(lb.PrevInAEL, lb.Bot);
        this.AddJoin(Op1, Op2, lb.Top);
      }
      if (lb.NextInAEL != rb)
      {
        if (rb.OutIdx >= 0 && rb.PrevInAEL.OutIdx >= 0 &&
          ClipperLib.ClipperBase.SlopesEqual(rb.PrevInAEL, rb) &&
          rb.WindDelta !== 0 && rb.PrevInAEL.WindDelta !== 0)
        {
          var Op2 = this.AddOutPt(rb.PrevInAEL, rb.Bot);
          this.AddJoin(Op1, Op2, rb.Top);
        }
        var e = lb.NextInAEL;
        if (e !== null)
          while (e != rb)
          {
            //nb: For calculating winding counts etc, IntersectEdges() assumes
            //that param1 will be to the right of param2 ABOVE the intersection ...

            this.IntersectEdges(rb, e, lb.Curr, true);
            //order important here
            e = e.NextInAEL;
          }
      }
    }
  };

  ClipperLib.Clipper.prototype.InsertEdgeIntoAEL = function (edge, startEdge)
  {
    if (this.m_ActiveEdges === null)
    {
      edge.PrevInAEL = null;
      edge.NextInAEL = null;
      this.m_ActiveEdges = edge;
    }
    else if (startEdge === null && this.E2InsertsBeforeE1(this.m_ActiveEdges, edge))
    {
      edge.PrevInAEL = null;
      edge.NextInAEL = this.m_ActiveEdges;
      this.m_ActiveEdges.PrevInAEL = edge;
      this.m_ActiveEdges = edge;
    }
    else
    {
      if (startEdge === null)
        startEdge = this.m_ActiveEdges;
      while (startEdge.NextInAEL !== null && !this.E2InsertsBeforeE1(startEdge.NextInAEL, edge))
        startEdge = startEdge.NextInAEL;
      edge.NextInAEL = startEdge.NextInAEL;
      if (startEdge.NextInAEL !== null)
        startEdge.NextInAEL.PrevInAEL = edge;
      edge.PrevInAEL = startEdge;
      startEdge.NextInAEL = edge;
    }
  };

  ClipperLib.Clipper.prototype.E2InsertsBeforeE1 = function (e1, e2)
  {
    if (e2.Curr.X == e1.Curr.X)
    {
      if (e2.Top.Y > e1.Top.Y)
        return e2.Top.X < ClipperLib.Clipper.TopX(e1, e2.Top.Y);
      else
        return e1.Top.X > ClipperLib.Clipper.TopX(e2, e1.Top.Y);
    }
    else
      return e2.Curr.X < e1.Curr.X;
  };

  ClipperLib.Clipper.prototype.IsEvenOddFillType = function (edge)
  {
    if (edge.PolyTyp == ClipperLib.PolyType.ptSubject)
      return this.m_SubjFillType == ClipperLib.PolyFillType.pftEvenOdd;
    else
      return this.m_ClipFillType == ClipperLib.PolyFillType.pftEvenOdd;
  };

  ClipperLib.Clipper.prototype.IsEvenOddAltFillType = function (edge)
  {
    if (edge.PolyTyp == ClipperLib.PolyType.ptSubject)
      return this.m_ClipFillType == ClipperLib.PolyFillType.pftEvenOdd;
    else
      return this.m_SubjFillType == ClipperLib.PolyFillType.pftEvenOdd;
  };

  ClipperLib.Clipper.prototype.IsContributing = function (edge)
  {
    var pft, pft2;
    if (edge.PolyTyp == ClipperLib.PolyType.ptSubject)
    {
      pft = this.m_SubjFillType;
      pft2 = this.m_ClipFillType;
    }
    else
    {
      pft = this.m_ClipFillType;
      pft2 = this.m_SubjFillType;
    }
    switch (pft)
    {
    case ClipperLib.PolyFillType.pftEvenOdd:
      if (edge.WindDelta === 0 && edge.WindCnt != 1)
        return false;
      break;
    case ClipperLib.PolyFillType.pftNonZero:
      if (Math.abs(edge.WindCnt) != 1)
        return false;
      break;
    case ClipperLib.PolyFillType.pftPositive:
      if (edge.WindCnt != 1)
        return false;
      break;
    default:
      if (edge.WindCnt != -1)
        return false;
      break;
    }
    switch (this.m_ClipType)
    {
    case ClipperLib.ClipType.ctIntersection:
      switch (pft2)
      {
      case ClipperLib.PolyFillType.pftEvenOdd:
      case ClipperLib.PolyFillType.pftNonZero:
        return (edge.WindCnt2 !== 0);
      case ClipperLib.PolyFillType.pftPositive:
        return (edge.WindCnt2 > 0);
      default:
        return (edge.WindCnt2 < 0);
      }
    case ClipperLib.ClipType.ctUnion:
      switch (pft2)
      {
      case ClipperLib.PolyFillType.pftEvenOdd:
      case ClipperLib.PolyFillType.pftNonZero:
        return (edge.WindCnt2 === 0);
      case ClipperLib.PolyFillType.pftPositive:
        return (edge.WindCnt2 <= 0);
      default:
        return (edge.WindCnt2 >= 0);
      }
    case ClipperLib.ClipType.ctDifference:
      if (edge.PolyTyp == ClipperLib.PolyType.ptSubject)
        switch (pft2)
        {
        case ClipperLib.PolyFillType.pftEvenOdd:
        case ClipperLib.PolyFillType.pftNonZero:
          return (edge.WindCnt2 === 0);
        case ClipperLib.PolyFillType.pftPositive:
          return (edge.WindCnt2 <= 0);
        default:
          return (edge.WindCnt2 >= 0);
        }
      else
        switch (pft2)
        {
        case ClipperLib.PolyFillType.pftEvenOdd:
        case ClipperLib.PolyFillType.pftNonZero:
          return (edge.WindCnt2 !== 0);
        case ClipperLib.PolyFillType.pftPositive:
          return (edge.WindCnt2 > 0);
        default:
          return (edge.WindCnt2 < 0);
        }
    case ClipperLib.ClipType.ctXor:
      if (edge.WindDelta === 0)
        switch (pft2)
        {
        case ClipperLib.PolyFillType.pftEvenOdd:
        case ClipperLib.PolyFillType.pftNonZero:
          return (edge.WindCnt2 === 0);
        case ClipperLib.PolyFillType.pftPositive:
          return (edge.WindCnt2 <= 0);
        default:
          return (edge.WindCnt2 >= 0);
        }
      else
        return true;
    }
    return true;
  };

  ClipperLib.Clipper.prototype.SetWindingCount = function (edge)
  {
    var e = edge.PrevInAEL;
    //find the edge of the same polytype that immediately preceeds 'edge' in AEL
    while (e !== null && ((e.PolyTyp != edge.PolyTyp) || (e.WindDelta === 0)))
      e = e.PrevInAEL;
    if (e === null)
    {
      edge.WindCnt = (edge.WindDelta === 0 ? 1 : edge.WindDelta);
      edge.WindCnt2 = 0;
      e = this.m_ActiveEdges;
      //ie get ready to calc WindCnt2
    }
    else if (edge.WindDelta === 0 && this.m_ClipType != ClipperLib.ClipType.ctUnion)
    {
      edge.WindCnt = 1;
      edge.WindCnt2 = e.WindCnt2;
      e = e.NextInAEL;
      //ie get ready to calc WindCnt2
    }
    else if (this.IsEvenOddFillType(edge))
    {
      //EvenOdd filling ...
      if (edge.WindDelta === 0)
      {
        //are we inside a subj polygon ...
        var Inside = true;
        var e2 = e.PrevInAEL;
        while (e2 !== null)
        {
          if (e2.PolyTyp == e.PolyTyp && e2.WindDelta !== 0)
            Inside = !Inside;
          e2 = e2.PrevInAEL;
        }
        edge.WindCnt = (Inside ? 0 : 1);
      }
      else
      {
        edge.WindCnt = edge.WindDelta;
      }
      edge.WindCnt2 = e.WindCnt2;
      e = e.NextInAEL;
      //ie get ready to calc WindCnt2
    }
    else
    {
      //nonZero, Positive or Negative filling ...
      if (e.WindCnt * e.WindDelta < 0)
      {
        //prev edge is 'decreasing' WindCount (WC) toward zero
        //so we're outside the previous polygon ...
        if (Math.abs(e.WindCnt) > 1)
        {
          //outside prev poly but still inside another.
          //when reversing direction of prev poly use the same WC 
          if (e.WindDelta * edge.WindDelta < 0)
            edge.WindCnt = e.WindCnt;
          else
            edge.WindCnt = e.WindCnt + edge.WindDelta;
        }
        else
          edge.WindCnt = (edge.WindDelta === 0 ? 1 : edge.WindDelta);
      }
      else
      {
        //prev edge is 'increasing' WindCount (WC) away from zero
        //so we're inside the previous polygon ...
        if (edge.WindDelta === 0)
          edge.WindCnt = (e.WindCnt < 0 ? e.WindCnt - 1 : e.WindCnt + 1);
        else if (e.WindDelta * edge.WindDelta < 0)
          edge.WindCnt = e.WindCnt;
        else
          edge.WindCnt = e.WindCnt + edge.WindDelta;
      }
      edge.WindCnt2 = e.WindCnt2;
      e = e.NextInAEL;
      //ie get ready to calc WindCnt2
    }
    //update WindCnt2 ...
    if (this.IsEvenOddAltFillType(edge))
    {
      //EvenOdd filling ...
      while (e != edge)
      {
        if (e.WindDelta !== 0)
          edge.WindCnt2 = (edge.WindCnt2 === 0 ? 1 : 0);
        e = e.NextInAEL;
      }
    }
    else
    {
      //nonZero, Positive or Negative filling ...
      while (e != edge)
      {
        edge.WindCnt2 += e.WindDelta;
        e = e.NextInAEL;
      }
    }
  };

  ClipperLib.Clipper.prototype.AddEdgeToSEL = function (edge)
  {
    //SEL pointers in PEdge are reused to build a list of horizontal edges.
    //However, we don't need to worry about order with horizontal edge processing.
    if (this.m_SortedEdges === null)
    {
      this.m_SortedEdges = edge;
      edge.PrevInSEL = null;
      edge.NextInSEL = null;
    }
    else
    {
      edge.NextInSEL = this.m_SortedEdges;
      edge.PrevInSEL = null;
      this.m_SortedEdges.PrevInSEL = edge;
      this.m_SortedEdges = edge;
    }
  };

  ClipperLib.Clipper.prototype.CopyAELToSEL = function ()
  {
    var e = this.m_ActiveEdges;
    this.m_SortedEdges = e;
    while (e !== null)
    {
      e.PrevInSEL = e.PrevInAEL;
      e.NextInSEL = e.NextInAEL;
      e = e.NextInAEL;
    }
  };

  ClipperLib.Clipper.prototype.SwapPositionsInAEL = function (edge1, edge2)
  {
    //check that one or other edge hasn't already been removed from AEL ...
    if (edge1.NextInAEL == edge1.PrevInAEL || edge2.NextInAEL == edge2.PrevInAEL)
      return;
    if (edge1.NextInAEL == edge2)
    {
      var next = edge2.NextInAEL;
      if (next !== null)
        next.PrevInAEL = edge1;
      var prev = edge1.PrevInAEL;
      if (prev !== null)
        prev.NextInAEL = edge2;
      edge2.PrevInAEL = prev;
      edge2.NextInAEL = edge1;
      edge1.PrevInAEL = edge2;
      edge1.NextInAEL = next;
    }
    else if (edge2.NextInAEL == edge1)
    {
      var next = edge1.NextInAEL;
      if (next !== null)
        next.PrevInAEL = edge2;
      var prev = edge2.PrevInAEL;
      if (prev !== null)
        prev.NextInAEL = edge1;
      edge1.PrevInAEL = prev;
      edge1.NextInAEL = edge2;
      edge2.PrevInAEL = edge1;
      edge2.NextInAEL = next;
    }
    else
    {
      var next = edge1.NextInAEL;
      var prev = edge1.PrevInAEL;
      edge1.NextInAEL = edge2.NextInAEL;
      if (edge1.NextInAEL !== null)
        edge1.NextInAEL.PrevInAEL = edge1;
      edge1.PrevInAEL = edge2.PrevInAEL;
      if (edge1.PrevInAEL !== null)
        edge1.PrevInAEL.NextInAEL = edge1;
      edge2.NextInAEL = next;
      if (edge2.NextInAEL !== null)
        edge2.NextInAEL.PrevInAEL = edge2;
      edge2.PrevInAEL = prev;
      if (edge2.PrevInAEL !== null)
        edge2.PrevInAEL.NextInAEL = edge2;
    }
    if (edge1.PrevInAEL === null)
      this.m_ActiveEdges = edge1;
    else if (edge2.PrevInAEL === null)
      this.m_ActiveEdges = edge2;
  };

  ClipperLib.Clipper.prototype.SwapPositionsInSEL = function (edge1, edge2)
  {
    if (edge1.NextInSEL === null && edge1.PrevInSEL === null)
      return;
    if (edge2.NextInSEL === null && edge2.PrevInSEL === null)
      return;
    if (edge1.NextInSEL == edge2)
    {
      var next = edge2.NextInSEL;
      if (next !== null)
        next.PrevInSEL = edge1;
      var prev = edge1.PrevInSEL;
      if (prev !== null)
        prev.NextInSEL = edge2;
      edge2.PrevInSEL = prev;
      edge2.NextInSEL = edge1;
      edge1.PrevInSEL = edge2;
      edge1.NextInSEL = next;
    }
    else if (edge2.NextInSEL == edge1)
    {
      var next = edge1.NextInSEL;
      if (next !== null)
        next.PrevInSEL = edge2;
      var prev = edge2.PrevInSEL;
      if (prev !== null)
        prev.NextInSEL = edge1;
      edge1.PrevInSEL = prev;
      edge1.NextInSEL = edge2;
      edge2.PrevInSEL = edge1;
      edge2.NextInSEL = next;
    }
    else
    {
      var next = edge1.NextInSEL;
      var prev = edge1.PrevInSEL;
      edge1.NextInSEL = edge2.NextInSEL;
      if (edge1.NextInSEL !== null)
        edge1.NextInSEL.PrevInSEL = edge1;
      edge1.PrevInSEL = edge2.PrevInSEL;
      if (edge1.PrevInSEL !== null)
        edge1.PrevInSEL.NextInSEL = edge1;
      edge2.NextInSEL = next;
      if (edge2.NextInSEL !== null)
        edge2.NextInSEL.PrevInSEL = edge2;
      edge2.PrevInSEL = prev;
      if (edge2.PrevInSEL !== null)
        edge2.PrevInSEL.NextInSEL = edge2;
    }
    if (edge1.PrevInSEL === null)
      this.m_SortedEdges = edge1;
    else if (edge2.PrevInSEL === null)
      this.m_SortedEdges = edge2;
  };

  ClipperLib.Clipper.prototype.AddLocalMaxPoly = function (e1, e2, pt)
  {
    this.AddOutPt(e1, pt);
    if (e2.WindDelta == 0) this.AddOutPt(e2, pt);
    if (e1.OutIdx == e2.OutIdx)
    {
      e1.OutIdx = ClipperLib.ClipperBase.Unassigned;
      e2.OutIdx = ClipperLib.ClipperBase.Unassigned;
    }
    else if (e1.OutIdx < e2.OutIdx)
      this.AppendPolygon(e1, e2);
    else
      this.AppendPolygon(e2, e1);
  };

  ClipperLib.Clipper.prototype.AddLocalMinPoly = function (e1, e2, pt)
  {
    var result;
    var e, prevE;
    if (ClipperLib.ClipperBase.IsHorizontal(e2) || (e1.Dx > e2.Dx))
    {
      result = this.AddOutPt(e1, pt);
      e2.OutIdx = e1.OutIdx;
      e1.Side = ClipperLib.EdgeSide.esLeft;
      e2.Side = ClipperLib.EdgeSide.esRight;
      e = e1;
      if (e.PrevInAEL == e2)
        prevE = e2.PrevInAEL;
      else
        prevE = e.PrevInAEL;
    }
    else
    {
      result = this.AddOutPt(e2, pt);
      e1.OutIdx = e2.OutIdx;
      e1.Side = ClipperLib.EdgeSide.esRight;
      e2.Side = ClipperLib.EdgeSide.esLeft;
      e = e2;
      if (e.PrevInAEL == e1)
        prevE = e1.PrevInAEL;
      else
        prevE = e.PrevInAEL;
    }
    if (prevE !== null && prevE.OutIdx >= 0 && (ClipperLib.Clipper.TopX(prevE, pt.Y) == ClipperLib.Clipper.TopX(e, pt.Y)) && ClipperLib.ClipperBase.SlopesEqual(e, prevE) && (e.WindDelta !== 0) && (prevE.WindDelta !== 0))
    {
      var outPt = this.AddOutPt(prevE, pt);
      this.AddJoin(result, outPt, e.Top);
    }
    return result;
  };

  ClipperLib.Clipper.prototype.CreateOutRec = function ()
  {
    var result = new ClipperLib.OutRec();
    result.Idx = ClipperLib.ClipperBase.Unassigned;
    result.IsHole = false;
    result.IsOpen = false;
    result.FirstLeft = null;
    result.Pts = null;
    result.BottomPt = null;
    result.PolyNode = null;
    this.m_PolyOuts.push(result);
    result.Idx = this.m_PolyOuts.length - 1;
    return result;
  };

  ClipperLib.Clipper.prototype.AddOutPt = function (e, pt)
  {
    var ToFront = (e.Side == ClipperLib.EdgeSide.esLeft);

    if (e.OutIdx < 0)
    {
      var outRec = this.CreateOutRec();
      outRec.IsOpen = (e.WindDelta === 0);
      var newOp = new ClipperLib.OutPt();
      outRec.Pts = newOp;
      newOp.Idx = outRec.Idx;

      //newOp.Pt = pt;
      newOp.Pt.X = pt.X;
      newOp.Pt.Y = pt.Y;

      newOp.Next = newOp;
      newOp.Prev = newOp;
      if (!outRec.IsOpen)
        this.SetHoleState(e, outRec);
      if (use_xyz)
      {
        if (ClipperLib.FPoint.op_Equality(pt, e.Bot))
        {
          //newOp.Pt = e.Bot;
          newOp.Pt.X = e.Bot.X;
          newOp.Pt.Y = e.Bot.Y;
          newOp.Pt.Z = e.Bot.Z;
        }
        else if (ClipperLib.FPoint.op_Equality(pt, e.Top))
        {
          //newOp.Pt = e.Top;
          newOp.Pt.X = e.Top.X;
          newOp.Pt.Y = e.Top.Y;
          newOp.Pt.Z = e.Top.Z;
        }
        else
          this.SetZ(newOp.Pt, e);
      }
      e.OutIdx = outRec.Idx;
      //nb: do this after SetZ !
      return newOp;
    }
    else
    {
      var outRec = this.m_PolyOuts[e.OutIdx];
      //OutRec.Pts is the 'Left-most' point & OutRec.Pts.Prev is the 'Right-most'
      var op = outRec.Pts;
      if (ToFront && ClipperLib.FPoint.op_Equality(pt, op.Pt))
        return op;
      else if (!ToFront && ClipperLib.FPoint.op_Equality(pt, op.Prev.Pt))
        return op.Prev;
      var newOp = new ClipperLib.OutPt();
      newOp.Idx = outRec.Idx;

      //newOp.Pt = pt;
      newOp.Pt.X = pt.X;
      newOp.Pt.Y = pt.Y;

      newOp.Next = op;
      newOp.Prev = op.Prev;
      newOp.Prev.Next = newOp;
      op.Prev = newOp;
      if (ToFront)
        outRec.Pts = newOp;
      if (use_xyz)
      {
        if (ClipperLib.FPoint.op_Equality(pt, e.Bot))
        {
          //newOp.Pt = e.Bot;
          newOp.Pt.X = e.Bot.X;
          newOp.Pt.Y = e.Bot.Y;
          newOp.Pt.Z = e.Bot.Z;
        }
        else if (ClipperLib.FPoint.op_Equality(pt, e.Top))
        {
          //newOp.Pt = e.Top;
          newOp.Pt.X = e.Top.X;
          newOp.Pt.Y = e.Top.Y;
          newOp.Pt.Z = e.Top.Z;
        }
        else
          this.SetZ(newOp.Pt, e);
      }
      return newOp;
    }
  };

  ClipperLib.Clipper.prototype.SwapPoints = function (pt1, pt2)
  {
    var tmp = new ClipperLib.FPoint(pt1.Value);
    //pt1.Value = pt2.Value;
    pt1.Value.X = pt2.Value.X;
    pt1.Value.Y = pt2.Value.Y;
    //pt2.Value = tmp;
    pt2.Value.X = tmp.X;
    pt2.Value.Y = tmp.Y;
  };

  ClipperLib.Clipper.prototype.HorzSegmentsOverlap = function (Pt1a, Pt1b, Pt2a, Pt2b)
  {
    //precondition: both segments are horizontal
    if ((Pt1a.X > Pt2a.X) == (Pt1a.X < Pt2b.X))
      return true;
    else if ((Pt1b.X > Pt2a.X) == (Pt1b.X < Pt2b.X))
      return true;
    else if ((Pt2a.X > Pt1a.X) == (Pt2a.X < Pt1b.X))
      return true;
    else if ((Pt2b.X > Pt1a.X) == (Pt2b.X < Pt1b.X))
      return true;
    else if ((Pt1a.X == Pt2a.X) && (Pt1b.X == Pt2b.X))
      return true;
    else if ((Pt1a.X == Pt2b.X) && (Pt1b.X == Pt2a.X))
      return true;
    else
      return false;
  };

  ClipperLib.Clipper.prototype.InsertPolyPtBetween = function (p1, p2, pt)
  {
    var result = new ClipperLib.OutPt();
    //result.Pt = pt;
    result.Pt.X = pt.X;
    result.Pt.Y = pt.Y;

    if (p2 == p1.Next)
    {
      p1.Next = result;
      p2.Prev = result;
      result.Next = p2;
      result.Prev = p1;
    }
    else
    {
      p2.Next = result;
      p1.Prev = result;
      result.Next = p1;
      result.Prev = p2;
    }
    return result;
  };

  ClipperLib.Clipper.prototype.SetHoleState = function (e, outRec)
  {
    var isHole = false;
    var e2 = e.PrevInAEL;
    while (e2 !== null)
    {
      if (e2.OutIdx >= 0 && e2.WindDelta != 0)
      {
        isHole = !isHole;
        if (outRec.FirstLeft === null)
          outRec.FirstLeft = this.m_PolyOuts[e2.OutIdx];
      }
      e2 = e2.PrevInAEL;
    }
    if (isHole)
      outRec.IsHole = true;
  };

  ClipperLib.Clipper.prototype.GetDx = function (pt1, pt2)
  {
    if (pt1.Y == pt2.Y)
      return ClipperLib.ClipperBase.horizontal;
    else
      return (pt2.X - pt1.X) / (pt2.Y - pt1.Y);
  };

  ClipperLib.Clipper.prototype.FirstIsBottomPt = function (btmPt1, btmPt2)
  {
    var p = btmPt1.Prev;
    while ((ClipperLib.FPoint.op_Equality(p.Pt, btmPt1.Pt)) && (p != btmPt1))
      p = p.Prev;
    var dx1p = Math.abs(this.GetDx(btmPt1.Pt, p.Pt));
    p = btmPt1.Next;
    while ((ClipperLib.FPoint.op_Equality(p.Pt, btmPt1.Pt)) && (p != btmPt1))
      p = p.Next;
    var dx1n = Math.abs(this.GetDx(btmPt1.Pt, p.Pt));
    p = btmPt2.Prev;
    while ((ClipperLib.FPoint.op_Equality(p.Pt, btmPt2.Pt)) && (p != btmPt2))
      p = p.Prev;
    var dx2p = Math.abs(this.GetDx(btmPt2.Pt, p.Pt));
    p = btmPt2.Next;
    while ((ClipperLib.FPoint.op_Equality(p.Pt, btmPt2.Pt)) && (p != btmPt2))
      p = p.Next;
    var dx2n = Math.abs(this.GetDx(btmPt2.Pt, p.Pt));
    return (dx1p >= dx2p && dx1p >= dx2n) || (dx1n >= dx2p && dx1n >= dx2n);
  };

  ClipperLib.Clipper.prototype.GetBottomPt = function (pp)
  {
    var dups = null;
    var p = pp.Next;
    while (p != pp)
    {
      if (p.Pt.Y > pp.Pt.Y)
      {
        pp = p;
        dups = null;
      }
      else if (p.Pt.Y == pp.Pt.Y && p.Pt.X <= pp.Pt.X)
      {
        if (p.Pt.X < pp.Pt.X)
        {
          dups = null;
          pp = p;
        }
        else
        {
          if (p.Next != pp && p.Prev != pp)
            dups = p;
        }
      }
      p = p.Next;
    }
    if (dups !== null)
    {
      //there appears to be at least 2 vertices at bottomPt so ...
      while (dups != p)
      {
        if (!this.FirstIsBottomPt(p, dups))
          pp = dups;
        dups = dups.Next;
        while (ClipperLib.FPoint.op_Inequality(dups.Pt, pp.Pt))
          dups = dups.Next;
      }
    }
    return pp;
  };

  ClipperLib.Clipper.prototype.GetLowermostRec = function (outRec1, outRec2)
  {
    //work out which polygon fragment has the correct hole state ...
    if (outRec1.BottomPt === null)
      outRec1.BottomPt = this.GetBottomPt(outRec1.Pts);
    if (outRec2.BottomPt === null)
      outRec2.BottomPt = this.GetBottomPt(outRec2.Pts);
    var bPt1 = outRec1.BottomPt;
    var bPt2 = outRec2.BottomPt;
    if (bPt1.Pt.Y > bPt2.Pt.Y)
      return outRec1;
    else if (bPt1.Pt.Y < bPt2.Pt.Y)
      return outRec2;
    else if (bPt1.Pt.X < bPt2.Pt.X)
      return outRec1;
    else if (bPt1.Pt.X > bPt2.Pt.X)
      return outRec2;
    else if (bPt1.Next == bPt1)
      return outRec2;
    else if (bPt2.Next == bPt2)
      return outRec1;
    else if (this.FirstIsBottomPt(bPt1, bPt2))
      return outRec1;
    else
      return outRec2;
  };

  ClipperLib.Clipper.prototype.Param1RightOfParam2 = function (outRec1, outRec2)
  {
    do {
      outRec1 = outRec1.FirstLeft;
      if (outRec1 == outRec2)
        return true;
    }
    while (outRec1 !== null)
    return false;
  };

  ClipperLib.Clipper.prototype.GetOutRec = function (idx)
  {
    var outrec = this.m_PolyOuts[idx];
    while (outrec != this.m_PolyOuts[outrec.Idx])
      outrec = this.m_PolyOuts[outrec.Idx];
    return outrec;
  };

  ClipperLib.Clipper.prototype.AppendPolygon = function (e1, e2)
  {
    //get the start and ends of both output polygons ...
    var outRec1 = this.m_PolyOuts[e1.OutIdx];
    var outRec2 = this.m_PolyOuts[e2.OutIdx];
    var holeStateRec;
    if (this.Param1RightOfParam2(outRec1, outRec2))
      holeStateRec = outRec2;
    else if (this.Param1RightOfParam2(outRec2, outRec1))
      holeStateRec = outRec1;
    else
      holeStateRec = this.GetLowermostRec(outRec1, outRec2);

    var p1_lft = outRec1.Pts;
    var p1_rt = p1_lft.Prev;
    var p2_lft = outRec2.Pts;
    var p2_rt = p2_lft.Prev;

    var side;
    //join e2 poly onto e1 poly and delete pointers to e2 ...
    if (e1.Side == ClipperLib.EdgeSide.esLeft)
    {
      if (e2.Side == ClipperLib.EdgeSide.esLeft)
      {
        //z y x a b c
        this.ReversePolyPtLinks(p2_lft);
        p2_lft.Next = p1_lft;
        p1_lft.Prev = p2_lft;
        p1_rt.Next = p2_rt;
        p2_rt.Prev = p1_rt;
        outRec1.Pts = p2_rt;
      }
      else
      {
        //x y z a b c
        p2_rt.Next = p1_lft;
        p1_lft.Prev = p2_rt;
        p2_lft.Prev = p1_rt;
        p1_rt.Next = p2_lft;
        outRec1.Pts = p2_lft;
      }
      side = ClipperLib.EdgeSide.esLeft;
    }
    else
    {
      if (e2.Side == ClipperLib.EdgeSide.esRight)
      {
        //a b c z y x
        this.ReversePolyPtLinks(p2_lft);
        p1_rt.Next = p2_rt;
        p2_rt.Prev = p1_rt;
        p2_lft.Next = p1_lft;
        p1_lft.Prev = p2_lft;
      }
      else
      {
        //a b c x y z
        p1_rt.Next = p2_lft;
        p2_lft.Prev = p1_rt;
        p1_lft.Prev = p2_rt;
        p2_rt.Next = p1_lft;
      }
      side = ClipperLib.EdgeSide.esRight;
    }
    outRec1.BottomPt = null;
    if (holeStateRec == outRec2)
    {
      if (outRec2.FirstLeft != outRec1)
        outRec1.FirstLeft = outRec2.FirstLeft;
      outRec1.IsHole = outRec2.IsHole;
    }
    outRec2.Pts = null;
    outRec2.BottomPt = null;

    outRec2.FirstLeft = outRec1;

    var OKIdx = e1.OutIdx;
    var ObsoleteIdx = e2.OutIdx;

    e1.OutIdx = ClipperLib.ClipperBase.Unassigned;
    //nb: safe because we only get here via AddLocalMaxPoly
    e2.OutIdx = ClipperLib.ClipperBase.Unassigned;

    var e = this.m_ActiveEdges;
    while (e !== null)
    {
      if (e.OutIdx == ObsoleteIdx)
      {
        e.OutIdx = OKIdx;
        e.Side = side;
        break;
      }
      e = e.NextInAEL;
    }
    outRec2.Idx = outRec1.Idx;
  };

  ClipperLib.Clipper.prototype.ReversePolyPtLinks = function (pp)
  {
    if (pp === null)
      return;
    var pp1;
    var pp2;
    pp1 = pp;
    do {
      pp2 = pp1.Next;
      pp1.Next = pp1.Prev;
      pp1.Prev = pp2;
      pp1 = pp2;
    }
    while (pp1 != pp)
  };

  ClipperLib.Clipper.SwapSides = function (edge1, edge2)
  {
    var side = edge1.Side;
    edge1.Side = edge2.Side;
    edge2.Side = side;
  };

  ClipperLib.Clipper.SwapPolyIndexes = function (edge1, edge2)
  {
    var outIdx = edge1.OutIdx;
    edge1.OutIdx = edge2.OutIdx;
    edge2.OutIdx = outIdx;
  };

  ClipperLib.Clipper.prototype.IntersectEdges = function (e1, e2, pt, protect)
  {
    //e1 will be to the left of e2 BELOW the intersection. Therefore e1 is before
    //e2 in AEL except when e1 is being inserted at the intersection point ...

    var e1stops = !protect && e1.NextInLML == null && (ClipperLib.FPoint.op_Equality(e1.Top, pt));
    var e2stops = !protect && e2.NextInLML == null && (ClipperLib.FPoint.op_Equality(e2.Top, pt));

    var e1Contributing = (e1.OutIdx >= 0);
    var e2Contributing = (e2.OutIdx >= 0);
    if (use_lines)
    {
      //if either edge is on an OPEN path ...
      if (e1.WindDelta === 0 || e2.WindDelta === 0)
      {
        //ignore subject-subject open path intersections UNLESS they
        //are both open paths, AND they are both 'contributing maximas' ...
        if (e1.WindDelta === 0 && e2.WindDelta === 0)
        {
          if ((e1stops || e2stops) && e1Contributing && e2Contributing)
            this.AddLocalMaxPoly(e1, e2, pt);
        }
        //if intersecting a subj line with a subj poly ...
        else if (e1.PolyTyp == e2.PolyTyp &&
          e1.WindDelta != e2.WindDelta && this.m_ClipType == ClipperLib.ClipType.ctUnion)
        {
          if (e1.WindDelta === 0)
          {
            if (e2Contributing)
            {
              this.AddOutPt(e1, pt);
              if (e1Contributing)
                e1.OutIdx = ClipperLib.ClipperBase.Unassigned;
            }
          }
          else
          {
            if (e1Contributing)
            {
              this.AddOutPt(e2, pt);
              if (e2Contributing)
                e2.OutIdx = ClipperLib.ClipperBase.Unassigned;
            }
          }
        }
        else if (e1.PolyTyp != e2.PolyTyp)
        {
          if ((e1.WindDelta === 0) && Math.abs(e2.WindCnt) == 1 &&
            (this.m_ClipType != ClipperLib.ClipType.ctUnion || e2.WindCnt2 === 0))
          {
            this.AddOutPt(e1, pt);
            if (e1Contributing)
              e1.OutIdx = ClipperLib.ClipperBase.Unassigned;
          }
          else if ((e2.WindDelta === 0) && (Math.abs(e1.WindCnt) == 1) &&
            (this.m_ClipType != ClipperLib.ClipType.ctUnion || e1.WindCnt2 === 0))
          {
            this.AddOutPt(e2, pt);
            if (e2Contributing)
              e2.OutIdx = ClipperLib.ClipperBase.Unassigned;
          }
        }
        if (e1stops)
          if (e1.OutIdx < 0)
            this.DeleteFromAEL(e1);
          else
            ClipperLib.Error("Error intersecting polylines");
        if (e2stops)
          if (e2.OutIdx < 0)
            this.DeleteFromAEL(e2);
          else
            ClipperLib.Error("Error intersecting polylines");
        return;
      }
    }
    //update winding counts...
    //assumes that e1 will be to the Right of e2 ABOVE the intersection
    if (e1.PolyTyp == e2.PolyTyp)
    {
      if (this.IsEvenOddFillType(e1))
      {
        var oldE1WindCnt = e1.WindCnt;
        e1.WindCnt = e2.WindCnt;
        e2.WindCnt = oldE1WindCnt;
      }
      else
      {
        if (e1.WindCnt + e2.WindDelta === 0)
          e1.WindCnt = -e1.WindCnt;
        else
          e1.WindCnt += e2.WindDelta;
        if (e2.WindCnt - e1.WindDelta === 0)
          e2.WindCnt = -e2.WindCnt;
        else
          e2.WindCnt -= e1.WindDelta;
      }
    }
    else
    {
      if (!this.IsEvenOddFillType(e2))
        e1.WindCnt2 += e2.WindDelta;
      else
        e1.WindCnt2 = (e1.WindCnt2 === 0) ? 1 : 0;
      if (!this.IsEvenOddFillType(e1))
        e2.WindCnt2 -= e1.WindDelta;
      else
        e2.WindCnt2 = (e2.WindCnt2 === 0) ? 1 : 0;
    }
    var e1FillType, e2FillType, e1FillType2, e2FillType2;
    if (e1.PolyTyp == ClipperLib.PolyType.ptSubject)
    {
      e1FillType = this.m_SubjFillType;
      e1FillType2 = this.m_ClipFillType;
    }
    else
    {
      e1FillType = this.m_ClipFillType;
      e1FillType2 = this.m_SubjFillType;
    }
    if (e2.PolyTyp == ClipperLib.PolyType.ptSubject)
    {
      e2FillType = this.m_SubjFillType;
      e2FillType2 = this.m_ClipFillType;
    }
    else
    {
      e2FillType = this.m_ClipFillType;
      e2FillType2 = this.m_SubjFillType;
    }
    var e1Wc, e2Wc;
    switch (e1FillType)
    {
    case ClipperLib.PolyFillType.pftPositive:
      e1Wc = e1.WindCnt;
      break;
    case ClipperLib.PolyFillType.pftNegative:
      e1Wc = -e1.WindCnt;
      break;
    default:
      e1Wc = Math.abs(e1.WindCnt);
      break;
    }
    switch (e2FillType)
    {
    case ClipperLib.PolyFillType.pftPositive:
      e2Wc = e2.WindCnt;
      break;
    case ClipperLib.PolyFillType.pftNegative:
      e2Wc = -e2.WindCnt;
      break;
    default:
      e2Wc = Math.abs(e2.WindCnt);
      break;
    }
    if (e1Contributing && e2Contributing)
    {
      if (e1stops || e2stops || (e1Wc !== 0 && e1Wc != 1) || (e2Wc !== 0 && e2Wc != 1) ||
        (e1.PolyTyp != e2.PolyTyp && this.m_ClipType != ClipperLib.ClipType.ctXor))
        this.AddLocalMaxPoly(e1, e2, pt);
      else
      {
        this.AddOutPt(e1, pt);
        this.AddOutPt(e2, pt);
        ClipperLib.Clipper.SwapSides(e1, e2);
        ClipperLib.Clipper.SwapPolyIndexes(e1, e2);
      }
    }
    else if (e1Contributing)
    {
      if (e2Wc === 0 || e2Wc == 1)
      {
        this.AddOutPt(e1, pt);
        ClipperLib.Clipper.SwapSides(e1, e2);
        ClipperLib.Clipper.SwapPolyIndexes(e1, e2);
      }
    }
    else if (e2Contributing)
    {
      if (e1Wc === 0 || e1Wc == 1)
      {
        this.AddOutPt(e2, pt);
        ClipperLib.Clipper.SwapSides(e1, e2);
        ClipperLib.Clipper.SwapPolyIndexes(e1, e2);
      }
    }
    else if ((e1Wc === 0 || e1Wc == 1) &&
      (e2Wc === 0 || e2Wc == 1) && !e1stops && !e2stops)
    {
      //neither edge is currently contributing ...
      var e1Wc2, e2Wc2;
      switch (e1FillType2)
      {
      case ClipperLib.PolyFillType.pftPositive:
        e1Wc2 = e1.WindCnt2;
        break;
      case ClipperLib.PolyFillType.pftNegative:
        e1Wc2 = -e1.WindCnt2;
        break;
      default:
        e1Wc2 = Math.abs(e1.WindCnt2);
        break;
      }
      switch (e2FillType2)
      {
      case ClipperLib.PolyFillType.pftPositive:
        e2Wc2 = e2.WindCnt2;
        break;
      case ClipperLib.PolyFillType.pftNegative:
        e2Wc2 = -e2.WindCnt2;
        break;
      default:
        e2Wc2 = Math.abs(e2.WindCnt2);
        break;
      }
      if (e1.PolyTyp != e2.PolyTyp)
        this.AddLocalMinPoly(e1, e2, pt);
      else if (e1Wc == 1 && e2Wc == 1)
        switch (this.m_ClipType)
        {
        case ClipperLib.ClipType.ctIntersection:
          if (e1Wc2 > 0 && e2Wc2 > 0)
            this.AddLocalMinPoly(e1, e2, pt);
          break;
        case ClipperLib.ClipType.ctUnion:
          if (e1Wc2 <= 0 && e2Wc2 <= 0)
            this.AddLocalMinPoly(e1, e2, pt);
          break;
        case ClipperLib.ClipType.ctDifference:
          if (((e1.PolyTyp == ClipperLib.PolyType.ptClip) && (e1Wc2 > 0) && (e2Wc2 > 0)) ||
            ((e1.PolyTyp == ClipperLib.PolyType.ptSubject) && (e1Wc2 <= 0) && (e2Wc2 <= 0)))
            this.AddLocalMinPoly(e1, e2, pt);
          break;
        case ClipperLib.ClipType.ctXor:
          this.AddLocalMinPoly(e1, e2, pt);
          break;
        }
      else
        ClipperLib.Clipper.SwapSides(e1, e2);
    }
    if ((e1stops != e2stops) &&
      ((e1stops && (e1.OutIdx >= 0)) || (e2stops && (e2.OutIdx >= 0))))
    {
      ClipperLib.Clipper.SwapSides(e1, e2);
      ClipperLib.Clipper.SwapPolyIndexes(e1, e2);
    }
    //finally, delete any non-contributing maxima edges  ...
    if (e1stops) this.DeleteFromAEL(e1);
    if (e2stops) this.DeleteFromAEL(e2);
  };

  ClipperLib.Clipper.prototype.DeleteFromAEL = function (e)
  {
    var AelPrev = e.PrevInAEL;
    var AelNext = e.NextInAEL;
    if (AelPrev === null && AelNext === null && (e != this.m_ActiveEdges))
      return;
    //already deleted
    if (AelPrev !== null)
      AelPrev.NextInAEL = AelNext;
    else
      this.m_ActiveEdges = AelNext;
    if (AelNext !== null)
      AelNext.PrevInAEL = AelPrev;
    e.NextInAEL = null;
    e.PrevInAEL = null;
  };

  ClipperLib.Clipper.prototype.DeleteFromSEL = function (e)
  {
    var SelPrev = e.PrevInSEL;
    var SelNext = e.NextInSEL;
    if (SelPrev === null && SelNext === null && (e != this.m_SortedEdges))
      return;
    //already deleted
    if (SelPrev !== null)
      SelPrev.NextInSEL = SelNext;
    else
      this.m_SortedEdges = SelNext;
    if (SelNext !== null)
      SelNext.PrevInSEL = SelPrev;
    e.NextInSEL = null;
    e.PrevInSEL = null;
  };

  ClipperLib.Clipper.prototype.UpdateEdgeIntoAEL = function (e)
  {
    if (e.NextInLML === null)
      ClipperLib.Error("UpdateEdgeIntoAEL: invalid call");
    var AelPrev = e.PrevInAEL;
    var AelNext = e.NextInAEL;
    e.NextInLML.OutIdx = e.OutIdx;
    if (AelPrev !== null)
      AelPrev.NextInAEL = e.NextInLML;
    else
      this.m_ActiveEdges = e.NextInLML;
    if (AelNext !== null)
      AelNext.PrevInAEL = e.NextInLML;
    e.NextInLML.Side = e.Side;
    e.NextInLML.WindDelta = e.WindDelta;
    e.NextInLML.WindCnt = e.WindCnt;
    e.NextInLML.WindCnt2 = e.WindCnt2;
    e = e.NextInLML;

    //    e.Curr = e.Bot;
    e.Curr.X = e.Bot.X;
    e.Curr.Y = e.Bot.Y;


    e.PrevInAEL = AelPrev;
    e.NextInAEL = AelNext;
    if (!ClipperLib.ClipperBase.IsHorizontal(e))
      this.InsertScanbeam(e.Top.Y);
    return e;
  };

  ClipperLib.Clipper.prototype.ProcessHorizontals = function (isTopOfScanbeam)
  {
    var horzEdge = this.m_SortedEdges;
    while (horzEdge !== null)
    {
      this.DeleteFromSEL(horzEdge);
      this.ProcessHorizontal(horzEdge, isTopOfScanbeam);
      horzEdge = this.m_SortedEdges;
    }
  };

  ClipperLib.Clipper.prototype.GetHorzDirection = function (HorzEdge, $var)
  {
    if (HorzEdge.Bot.X < HorzEdge.Top.X)
    {
      $var.Left = HorzEdge.Bot.X;
      $var.Right = HorzEdge.Top.X;
      $var.Dir = ClipperLib.Direction.dLeftToRight;
    }
    else
    {
      $var.Left = HorzEdge.Top.X;
      $var.Right = HorzEdge.Bot.X;
      $var.Dir = ClipperLib.Direction.dRightToLeft;
    }
  };

  ClipperLib.Clipper.prototype.PrepareHorzJoins = function (horzEdge, isTopOfScanbeam)
  {
    //get the last Op for this horizontal edge
    //the point may be anywhere along the horizontal ...
    var outPt = this.m_PolyOuts[horzEdge.OutIdx].Pts;
    if (horzEdge.Side != ClipperLib.EdgeSide.esLeft)
      outPt = outPt.Prev;
    //First, match up overlapping horizontal edges (eg when one polygon's
    //intermediate horz edge overlaps an intermediate horz edge of another, or
    //when one polygon sits on top of another) ...
    for (var i = 0, ilen = this.m_GhostJoins.length; i < ilen; ++i)
    {
      var j = this.m_GhostJoins[i];
      if (this.HorzSegmentsOverlap(j.OutPt1.Pt, j.OffPt, horzEdge.Bot, horzEdge.Top))
        this.AddJoin(j.OutPt1, outPt, j.OffPt);
    }
    //Also, since horizontal edges at the top of one SB are often removed from
    //the AEL before we process the horizontal edges at the bottom of the next,
    //we need to create 'ghost' Join records of 'contrubuting' horizontals that
    //we can compare with horizontals at the bottom of the next SB.
    if (isTopOfScanbeam)
      if (ClipperLib.FPoint.op_Equality(outPt.Pt, horzEdge.Top))
        this.AddGhostJoin(outPt, horzEdge.Bot);
      else
        this.AddGhostJoin(outPt, horzEdge.Top);
  };

  ClipperLib.Clipper.prototype.ProcessHorizontal = function (horzEdge, isTopOfScanbeam)
  {
    var $var = {
      Dir: null,
      Left: null,
      Right: null
    };
    this.GetHorzDirection(horzEdge, $var);
    var dir = $var.Dir;
    var horzLeft = $var.Left;
    var horzRight = $var.Right;

    var eLastHorz = horzEdge,
      eMaxPair = null;
    while (eLastHorz.NextInLML !== null && ClipperLib.ClipperBase.IsHorizontal(eLastHorz.NextInLML))
      eLastHorz = eLastHorz.NextInLML;
    if (eLastHorz.NextInLML === null)
      eMaxPair = this.GetMaximaPair(eLastHorz);
    for (;;)
    {
      var IsLastHorz = (horzEdge == eLastHorz);
      var e = this.GetNextInAEL(horzEdge, dir);
      while (e !== null)
      {
        //Break if we've got to the end of an intermediate horizontal edge ...
        //nb: Smaller Dx's are to the right of larger Dx's ABOVE the horizontal.
        if (e.Curr.X == horzEdge.Top.X && horzEdge.NextInLML !== null && e.Dx < horzEdge.NextInLML.Dx)
          break;
        var eNext = this.GetNextInAEL(e, dir);
        //saves eNext for later
        if ((dir == ClipperLib.Direction.dLeftToRight && e.Curr.X <= horzRight) || (dir == ClipperLib.Direction.dRightToLeft && e.Curr.X >= horzLeft))
        {
          if (horzEdge.OutIdx >= 0 && horzEdge.WindDelta != 0)
            this.PrepareHorzJoins(horzEdge, isTopOfScanbeam);
          //so far we're still in range of the horizontal Edge  but make sure
          //we're at the last of consec. horizontals when matching with eMaxPair
          if (e == eMaxPair && IsLastHorz)
          {

            if (dir == ClipperLib.Direction.dLeftToRight)
              this.IntersectEdges(horzEdge, e, e.Top, false);
            else
              this.IntersectEdges(e, horzEdge, e.Top, false);
            if (eMaxPair.OutIdx >= 0)
              ClipperLib.Error("ProcessHorizontal error");
            return;
          }
          else if (dir == ClipperLib.Direction.dLeftToRight)
          {
            var Pt = new ClipperLib.FPoint(e.Curr.X, horzEdge.Curr.Y);
            this.IntersectEdges(horzEdge, e, Pt, true);
          }
          else
          {
            var Pt = new ClipperLib.FPoint(e.Curr.X, horzEdge.Curr.Y);
            this.IntersectEdges(e, horzEdge, Pt, true);
          }
          this.SwapPositionsInAEL(horzEdge, e);
        }
        else if ((dir == ClipperLib.Direction.dLeftToRight && e.Curr.X >= horzRight) || (dir == ClipperLib.Direction.dRightToLeft && e.Curr.X <= horzLeft))
          break;
        e = eNext;
      }
      //end while
      if (horzEdge.OutIdx >= 0 && horzEdge.WindDelta !== 0)
        this.PrepareHorzJoins(horzEdge, isTopOfScanbeam);
      if (horzEdge.NextInLML !== null && ClipperLib.ClipperBase.IsHorizontal(horzEdge.NextInLML))
      {
        horzEdge = this.UpdateEdgeIntoAEL(horzEdge);

        if (horzEdge.OutIdx >= 0)
          this.AddOutPt(horzEdge, horzEdge.Bot);

        var $var = {
          Dir: dir,
          Left: horzLeft,
          Right: horzRight
        };
        this.GetHorzDirection(horzEdge, $var);
        dir = $var.Dir;
        horzLeft = $var.Left;
        horzRight = $var.Right;

      }
      else
        break;
    }
    //end for (;;)
    if (horzEdge.NextInLML !== null)
    {
      if (horzEdge.OutIdx >= 0)
      {
        var op1 = this.AddOutPt(horzEdge, horzEdge.Top);

        horzEdge = this.UpdateEdgeIntoAEL(horzEdge);

        if (horzEdge.WindDelta === 0)
          return;
        //nb: HorzEdge is no longer horizontal here
        var ePrev = horzEdge.PrevInAEL;
        var eNext = horzEdge.NextInAEL;
        if (ePrev !== null && ePrev.Curr.X == horzEdge.Bot.X &&
          ePrev.Curr.Y == horzEdge.Bot.Y && ePrev.WindDelta !== 0 &&
          (ePrev.OutIdx >= 0 && ePrev.Curr.Y > ePrev.Top.Y &&
            ClipperLib.ClipperBase.SlopesEqual(horzEdge, ePrev)))
        {
          var op2 = this.AddOutPt(ePrev, horzEdge.Bot);
          this.AddJoin(op1, op2, horzEdge.Top);
        }
        else if (eNext !== null && eNext.Curr.X == horzEdge.Bot.X &&
          eNext.Curr.Y == horzEdge.Bot.Y && eNext.WindDelta !== 0 &&
          eNext.OutIdx >= 0 && eNext.Curr.Y > eNext.Top.Y &&
          ClipperLib.ClipperBase.SlopesEqual(horzEdge, eNext))
        {
          var op2 = this.AddOutPt(eNext, horzEdge.Bot);
          this.AddJoin(op1, op2, horzEdge.Top);
        }
      }
      else horzEdge = this.UpdateEdgeIntoAEL(horzEdge);
    }
    else if (eMaxPair !== null)
    {
      if (eMaxPair.OutIdx >= 0)
      {
        if (dir == ClipperLib.Direction.dLeftToRight)
          this.IntersectEdges(horzEdge, eMaxPair, horzEdge.Top, false);
        else
          this.IntersectEdges(eMaxPair, horzEdge, horzEdge.Top, false);
        if (eMaxPair.OutIdx >= 0)
          ClipperLib.Error("ProcessHorizontal error");
      }
      else
      {
        this.DeleteFromAEL(horzEdge);
        this.DeleteFromAEL(eMaxPair);
      }
    }
    else
    {
      if (horzEdge.OutIdx >= 0)
        this.AddOutPt(horzEdge, horzEdge.Top);
      this.DeleteFromAEL(horzEdge);
    }
  };

  ClipperLib.Clipper.prototype.GetNextInAEL = function (e, Direction)
  {
    return Direction == ClipperLib.Direction.dLeftToRight ? e.NextInAEL : e.PrevInAEL;
  };
  ClipperLib.Clipper.prototype.IsMinima = function (e)
  {
    return e !== null && (e.Prev.NextInLML != e) && (e.Next.NextInLML != e);
  };
  ClipperLib.Clipper.prototype.IsMaxima = function (e, Y)
  {
    return (e !== null && e.Top.Y == Y && e.NextInLML === null);
  };
  ClipperLib.Clipper.prototype.IsIntermediate = function (e, Y)
  {
    return (e.Top.Y == Y && e.NextInLML !== null);
  };

  ClipperLib.Clipper.prototype.GetMaximaPair = function (e)
  {
    var result = null;
    if ((ClipperLib.FPoint.op_Equality(e.Next.Top, e.Top)) && e.Next.NextInLML === null)
      result = e.Next;
    else if ((ClipperLib.FPoint.op_Equality(e.Prev.Top, e.Top)) && e.Prev.NextInLML === null)
      result = e.Prev;
    if (result !== null && (result.OutIdx == ClipperLib.ClipperBase.Skip || (result.NextInAEL == result.PrevInAEL && !ClipperLib.ClipperBase.IsHorizontal(result))))
      return null;
    return result;
  };

  ClipperLib.Clipper.prototype.ProcessIntersections = function (botY, topY)
  {
    if (this.m_ActiveEdges == null)
      return true;
    try
    {
      this.BuildIntersectList(botY, topY);
      if (this.m_IntersectList.length == 0)
        return true;
      if (this.m_IntersectList.length == 1 || this.FixupIntersectionOrder())
        this.ProcessIntersectList();
      else
        return false;
    }
    catch ($$e2)
    {
      this.m_SortedEdges = null;
      this.m_IntersectList.length = 0;
      ClipperLib.Error("ProcessIntersections error");
    }
    this.m_SortedEdges = null;
    return true;
  };

  ClipperLib.Clipper.prototype.BuildIntersectList = function (botY, topY)
  {
    if (this.m_ActiveEdges === null)
      return;
    //prepare for sorting ...
    var e = this.m_ActiveEdges;

    this.m_SortedEdges = e;
    while (e !== null)
    {
      e.PrevInSEL = e.PrevInAEL;
      e.NextInSEL = e.NextInAEL;
      e.Curr.X = ClipperLib.Clipper.TopX(e, topY);
      e = e.NextInAEL;
    }
    //bubblesort ...
    var isModified = true;
    var pt = new ClipperLib.FPoint(0, 0);
    while (isModified && this.m_SortedEdges !== null)
    {
      isModified = false;
      e = this.m_SortedEdges;
      while (e.NextInSEL !== null)
      {
        var eNext = e.NextInSEL;

        if (e.Curr.X > eNext.Curr.X)
        {
          this.IntersectPoint(e, eNext, pt);

          if (pt.Y > botY)
          {
            pt.Y = botY;
            if (Math.abs(e.Dx) > Math.abs(eNext.Dx))
              pt.X = ClipperLib.Clipper.TopX(eNext, botY);
            else
              pt.X = ClipperLib.Clipper.TopX(e, botY);
          }
          var newNode = new ClipperLib.IntersectNode();
          newNode.Edge1 = e;
          newNode.Edge2 = eNext;
          //newNode.Pt = pt;
          newNode.Pt.X = pt.X;
          newNode.Pt.Y = pt.Y;

          this.m_IntersectList.push(newNode);
          this.SwapPositionsInSEL(e, eNext);
          isModified = true;
        }
        else
          e = eNext;
      }
      if (e.PrevInSEL !== null)
        e.PrevInSEL.NextInSEL = null;
      else
        break;
    }
    this.m_SortedEdges = null;
  };

  ClipperLib.Clipper.prototype.EdgesAdjacent = function (inode)
  {
    return (inode.Edge1.NextInSEL == inode.Edge2) || (inode.Edge1.PrevInSEL == inode.Edge2);
  };

  ClipperLib.Clipper.IntersectNodeSort = function (node1, node2)
  {
    //the following typecast is safe because the differences in Pt.Y will
    //be limited to the height of the scanbeam.
    return (node2.Pt.Y - node1.Pt.Y);
  };

  ClipperLib.Clipper.prototype.FixupIntersectionOrder = function ()
  {
    //pre-condition: intersections are sorted bottom-most first.
    //Now it's crucial that intersections are made only between adjacent edges,
    //so to ensure this the order of intersections may need adjusting ...
    this.m_IntersectList.sort(this.m_IntersectNodeComparer);
    this.CopyAELToSEL();
    var cnt = this.m_IntersectList.length;
    for (var i = 0; i < cnt; i++)
    {
      if (!this.EdgesAdjacent(this.m_IntersectList[i]))
      {
        var j = i + 1;
        while (j < cnt && !this.EdgesAdjacent(this.m_IntersectList[j]))
          j++;
        if (j == cnt)
          return false;
        var tmp = this.m_IntersectList[i];
        this.m_IntersectList[i] = this.m_IntersectList[j];
        this.m_IntersectList[j] = tmp;
      }
      this.SwapPositionsInSEL(this.m_IntersectList[i].Edge1, this.m_IntersectList[i].Edge2);
    }
    return true;
  };

  ClipperLib.Clipper.prototype.ProcessIntersectList = function ()
  {
    for (var i = 0, ilen = this.m_IntersectList.length; i < ilen; i++)
    {
      var iNode = this.m_IntersectList[i];
      this.IntersectEdges(iNode.Edge1, iNode.Edge2, iNode.Pt, true);
      this.SwapPositionsInAEL(iNode.Edge1, iNode.Edge2);
    }
    this.m_IntersectList.length = 0;
  };

  ClipperLib.Clipper.TopX = function (edge, currentY)
  {
    if (currentY == edge.Top.Y)
      return edge.Top.X;
    return edge.Bot.X + edge.Dx * (currentY - edge.Bot.Y);
  };

  ClipperLib.Clipper.prototype.IntersectPoint = function (edge1, edge2, ip)
  {
    ip.X = 0;
    ip.Y = 0;
    var b1, b2;
    //nb: with very large coordinate values, it's possible for SlopesEqual() to 
    //return false but for the edge.Dx value be equal due to double precision rounding.
    if (ClipperLib.ClipperBase.SlopesEqual(edge1, edge2) || edge1.Dx == edge2.Dx)
    {
      if (edge2.Bot.Y > edge1.Bot.Y)
      {
        ip.X = edge2.Bot.X;
        ip.Y = edge2.Bot.Y;
      }
      else
      {
        ip.X = edge1.Bot.X;
        ip.Y = edge1.Bot.Y;
      }
      return false;
    }
    else if (edge1.Delta.X === 0)
    {
      ip.X = edge1.Bot.X;
      if (ClipperLib.ClipperBase.IsHorizontal(edge2))
      {
        ip.Y = edge2.Bot.Y;
      }
      else
      {
        b2 = edge2.Bot.Y - (edge2.Bot.X / edge2.Dx);
        ip.Y = ip.X / edge2.Dx + b2;
      }
    }
    else if (edge2.Delta.X === 0)
    {
      ip.X = edge2.Bot.X;
      if (ClipperLib.ClipperBase.IsHorizontal(edge1))
      {
        ip.Y = edge1.Bot.Y;
      }
      else
      {
        b1 = edge1.Bot.Y - (edge1.Bot.X / edge1.Dx);
        ip.Y = ip.X / edge1.Dx + b1;
      }
    }
    else
    {
      b1 = edge1.Bot.X - edge1.Bot.Y * edge1.Dx;
      b2 = edge2.Bot.X - edge2.Bot.Y * edge2.Dx;
      var q = (b2 - b1) / (edge1.Dx - edge2.Dx);
      ip.Y = q;
      if (Math.abs(edge1.Dx) < Math.abs(edge2.Dx))
        ip.X = edge1.Dx * q + b1;
      else
        ip.X = edge2.Dx * q + b2;
    }
    if (ip.Y < edge1.Top.Y || ip.Y < edge2.Top.Y)
    {
      if (edge1.Top.Y > edge2.Top.Y)
        ip.Y = edge1.Top.Y;
      else
        ip.Y = edge2.Top.Y;
      if (Math.abs(edge1.Dx) < Math.abs(edge2.Dx))
        ip.X = ClipperLib.Clipper.TopX(edge1, ip.Y);
      else
        ip.X = ClipperLib.Clipper.TopX(edge2, ip.Y);
    }
    return true;
  };

  ClipperLib.Clipper.prototype.ProcessEdgesAtTopOfScanbeam = function (topY)
  {
    var e = this.m_ActiveEdges;
    while (e !== null)
    {
      //1. process maxima, treating them as if they're 'bent' horizontal edges,
      //   but exclude maxima with horizontal edges. nb: e can't be a horizontal.
      var IsMaximaEdge = this.IsMaxima(e, topY);
      if (IsMaximaEdge)
      {
        var eMaxPair = this.GetMaximaPair(e);
        IsMaximaEdge = (eMaxPair === null || !ClipperLib.ClipperBase.IsHorizontal(eMaxPair));
      }
      if (IsMaximaEdge)
      {
        var ePrev = e.PrevInAEL;
        this.DoMaxima(e);
        if (ePrev === null)
          e = this.m_ActiveEdges;
        else
          e = ePrev.NextInAEL;
      }
      else
      {
        //2. promote horizontal edges, otherwise update Curr.X and Curr.Y ...
        if (this.IsIntermediate(e, topY) && ClipperLib.ClipperBase.IsHorizontal(e.NextInLML))
        {
          e = this.UpdateEdgeIntoAEL(e);
          if (e.OutIdx >= 0)
            this.AddOutPt(e, e.Bot);
          this.AddEdgeToSEL(e);
        }
        else
        {
          e.Curr.X = ClipperLib.Clipper.TopX(e, topY);
          e.Curr.Y = topY;
        }
        if (this.StrictlySimple)
        {
          var ePrev = e.PrevInAEL;

          if ((e.OutIdx >= 0) && (e.WindDelta !== 0) && ePrev !== null &&
            (ePrev.OutIdx >= 0) && (ePrev.Curr.X == e.Curr.X) &&
            (ePrev.WindDelta !== 0))
          {
            var op = this.AddOutPt(ePrev, e.Curr);
            var op2 = this.AddOutPt(e, e.Curr);
            this.AddJoin(op, op2, e.Curr);
            //StrictlySimple (type-3) join
          }
        }
        e = e.NextInAEL;
      }
    }
    //3. Process horizontals at the Top of the scanbeam ...
    this.ProcessHorizontals(true);

    //4. Promote intermediate vertices ...
    e = this.m_ActiveEdges;
    while (e !== null)
    {
      if (this.IsIntermediate(e, topY))
      {
        var op = null;
        if (e.OutIdx >= 0)
          op = this.AddOutPt(e, e.Top);
        e = this.UpdateEdgeIntoAEL(e);

        //if output polygons share an edge, they'll need joining later ...
        var ePrev = e.PrevInAEL;
        var eNext = e.NextInAEL;
        if (ePrev !== null && ePrev.Curr.X == e.Bot.X &&
          ePrev.Curr.Y == e.Bot.Y && op !== null &&
          ePrev.OutIdx >= 0 && ePrev.Curr.Y > ePrev.Top.Y &&
          ClipperLib.ClipperBase.SlopesEqual(e, ePrev) &&
          (e.WindDelta !== 0) && (ePrev.WindDelta !== 0))
        {
          var op2 = this.AddOutPt(ePrev, e.Bot);
          this.AddJoin(op, op2, e.Top);
        }
        else if (eNext !== null && eNext.Curr.X == e.Bot.X &&
          eNext.Curr.Y == e.Bot.Y && op !== null &&
          eNext.OutIdx >= 0 && eNext.Curr.Y > eNext.Top.Y &&
          ClipperLib.ClipperBase.SlopesEqual(e, eNext) &&
          (e.WindDelta !== 0) && (eNext.WindDelta !== 0))
        {
          var op2 = this.AddOutPt(eNext, e.Bot);
          this.AddJoin(op, op2, e.Top);
        }
      }
      e = e.NextInAEL;
    }
  };

  ClipperLib.Clipper.prototype.DoMaxima = function (e)
  {
    var eMaxPair = this.GetMaximaPair(e);
    if (eMaxPair === null)
    {
      if (e.OutIdx >= 0)
        this.AddOutPt(e, e.Top);
      this.DeleteFromAEL(e);
      return;
    }
    var eNext = e.NextInAEL;
    //var use_lines = true;
    while (eNext !== null && eNext != eMaxPair)
    {
      this.IntersectEdges(e, eNext, e.Top, true);
      this.SwapPositionsInAEL(e, eNext);
      eNext = e.NextInAEL;
    }
    if (e.OutIdx == ClipperLib.ClipperBase.Unassigned && eMaxPair.OutIdx == ClipperLib.ClipperBase.Unassigned)
    {
      this.DeleteFromAEL(e);
      this.DeleteFromAEL(eMaxPair);
    }
    else if (e.OutIdx >= 0 && eMaxPair.OutIdx >= 0)
    {
      this.IntersectEdges(e, eMaxPair, e.Top, false);
    }
    else if (use_lines && e.WindDelta === 0)
    {
      if (e.OutIdx >= 0)
      {
        this.AddOutPt(e, e.Top);
        e.OutIdx = ClipperLib.ClipperBase.Unassigned;
      }
      this.DeleteFromAEL(e);

      if (eMaxPair.OutIdx >= 0)
      {
        this.AddOutPt(eMaxPair, e.Top);
        eMaxPair.OutIdx = ClipperLib.ClipperBase.Unassigned;
      }
      this.DeleteFromAEL(eMaxPair);
    }
    else
      ClipperLib.Error("DoMaxima error");
  };

  ClipperLib.Clipper.ReversePaths = function (polys)
  {
    for (var i = 0, len = polys.length; i < len; i++)
      polys[i].reverse();
  };

  ClipperLib.Clipper.Orientation = function (poly)
  {
    return ClipperLib.Clipper.Area(poly) >= 0;
  };

  ClipperLib.Clipper.prototype.PointCount = function (pts)
  {
    if (pts === null)
      return 0;
    var result = 0;
    var p = pts;
    do {
      result++;
      p = p.Next;
    }
    while (p != pts)
    return result;
  };

  ClipperLib.Clipper.prototype.BuildResult = function (polyg)
  {
    // Timo's addition
    // In very rare cases there may become peaks
    // While I found the reason, this is a workaround
    //var r = this.GetBounds2();

    ClipperLib.Clear(polyg);
    for (var i = 0, ilen = this.m_PolyOuts.length; i < ilen; i++)
    {
      var outRec = this.m_PolyOuts[i];
      if (outRec.Pts === null)
        continue;
      var p = outRec.Pts.Prev;
      var cnt = this.PointCount(p);
      if (cnt < 2)
        continue;
      var pg = new Array();
      for (var j = 0; j < cnt; j++)
      {
        //if (p.Pt.Y >= r.top)
        //{
        pg.push(p.Pt);
        //}
        p = p.Prev;
      }
      polyg.push(pg);
    }
  };

  ClipperLib.Clipper.prototype.BuildResult2 = function (polytree)
  {
    // Timo's addition
    // In very rare cases there may become peaks
    // While I found the reason, this is a workaround
    //var r = this.GetBounds2();
    polytree.Clear();
    //add each output polygon/contour to polytree ...
    //polytree.m_AllPolys.set_Capacity(this.m_PolyOuts.length);
    for (var i = 0, ilen = this.m_PolyOuts.length; i < ilen; i++)
    {
      var outRec = this.m_PolyOuts[i];
      var cnt = this.PointCount(outRec.Pts);
      if ((outRec.IsOpen && cnt < 2) || (!outRec.IsOpen && cnt < 3))
        continue;

      this.FixHoleLinkage(outRec);
      var pn = new ClipperLib.PolyNode();
      polytree.m_AllPolys.push(pn);
      outRec.PolyNode = pn;
      pn.m_polygon.length = 0;
      var op = outRec.Pts.Prev;
      for (var j = 0; j < cnt; j++)
      {
        //if (op.Pt.Y >= r.top)
        //{
        pn.m_polygon.push(op.Pt);
        //}            
        op = op.Prev;
      }
    }
    //fixup PolyNode links etc ...
    //polytree.m_Childs.set_Capacity(this.m_PolyOuts.length);
    for (var i = 0, ilen = this.m_PolyOuts.length; i < ilen; i++)
    {
      var outRec = this.m_PolyOuts[i];
      if (outRec.PolyNode === null)
        continue;
      else if (outRec.IsOpen)
      {
        outRec.PolyNode.IsOpen = true;
        polytree.AddChild(outRec.PolyNode);
      }
      else if (outRec.FirstLeft !== null && outRec.FirstLeft.PolyNode != null)
        outRec.FirstLeft.PolyNode.AddChild(outRec.PolyNode);
      else
        polytree.AddChild(outRec.PolyNode);
    }
  };

  ClipperLib.Clipper.prototype.FixupOutPolygon = function (outRec)
  {
    //FixupOutPolygon() - removes duplicate points and simplifies consecutive
    //parallel edges by removing the middle vertex.
    var lastOK = null;
    outRec.BottomPt = null;
    var pp = outRec.Pts;
    for (;;)
    {
      if (pp.Prev == pp || pp.Prev == pp.Next)
      {
        this.DisposeOutPts(pp);
        outRec.Pts = null;
        return;
      }
      //test for duplicate points and collinear edges ...
      if ((ClipperLib.FPoint.op_Equality(pp.Pt, pp.Next.Pt)) || (ClipperLib.FPoint.op_Equality(pp.Pt, pp.Prev.Pt)) ||
        (ClipperLib.ClipperBase.SlopesEqual(pp.Prev.Pt, pp.Pt, pp.Next.Pt) &&
          (!this.PreserveCollinear || !this.Pt2IsBetweenPt1AndPt3(pp.Prev.Pt, pp.Pt, pp.Next.Pt))))
      {
        lastOK = null;
        var tmp = pp;
        pp.Prev.Next = pp.Next;
        pp.Next.Prev = pp.Prev;
        pp = pp.Prev;
        tmp = null;
      }
      else if (pp == lastOK)
        break;
      else
      {
        if (lastOK === null)
          lastOK = pp;
        pp = pp.Next;
      }
    }
    outRec.Pts = pp;
  };

  ClipperLib.Clipper.prototype.DupOutPt = function (outPt, InsertAfter)
  {
    var result = new ClipperLib.OutPt();
    //result.Pt = outPt.Pt;
    result.Pt.X = outPt.Pt.X;
    result.Pt.Y = outPt.Pt.Y;

    result.Idx = outPt.Idx;
    if (InsertAfter)
    {
      result.Next = outPt.Next;
      result.Prev = outPt;
      outPt.Next.Prev = result;
      outPt.Next = result;
    }
    else
    {
      result.Prev = outPt.Prev;
      result.Next = outPt;
      outPt.Prev.Next = result;
      outPt.Prev = result;
    }
    return result;
  };

  ClipperLib.Clipper.prototype.GetOverlap = function (a1, a2, b1, b2, $val)
  {
    if (a1 < a2)
    {
      if (b1 < b2)
      {
        $val.Left = Math.max(a1, b1);
        $val.Right = Math.min(a2, b2);
      }
      else
      {
        $val.Left = Math.max(a1, b2);
        $val.Right = Math.min(a2, b1);
      }
    }
    else
    {
      if (b1 < b2)
      {
        $val.Left = Math.max(a2, b1);
        $val.Right = Math.min(a1, b2);
      }
      else
      {
        $val.Left = Math.max(a2, b2);
        $val.Right = Math.min(a1, b1);
      }
    }
    return $val.Left < $val.Right;
  };

  ClipperLib.Clipper.prototype.JoinHorz = function (op1, op1b, op2, op2b, Pt, DiscardLeft)
  {
    var Dir1 = (op1.Pt.X > op1b.Pt.X ? ClipperLib.Direction.dRightToLeft : ClipperLib.Direction.dLeftToRight);
    var Dir2 = (op2.Pt.X > op2b.Pt.X ? ClipperLib.Direction.dRightToLeft : ClipperLib.Direction.dLeftToRight);
    if (Dir1 == Dir2)
      return false;
    //When DiscardLeft, we want Op1b to be on the Left of Op1, otherwise we
    //want Op1b to be on the Right. (And likewise with Op2 and Op2b.)
    //So, to facilitate this while inserting Op1b and Op2b ...
    //when DiscardLeft, make sure we're AT or RIGHT of Pt before adding Op1b,
    //otherwise make sure we're AT or LEFT of Pt. (Likewise with Op2b.)
    if (Dir1 == ClipperLib.Direction.dLeftToRight)
    {
      while (op1.Next.Pt.X <= Pt.X &&
        op1.Next.Pt.X >= op1.Pt.X && op1.Next.Pt.Y == Pt.Y)
        op1 = op1.Next;
      if (DiscardLeft && (op1.Pt.X != Pt.X))
        op1 = op1.Next;
      op1b = this.DupOutPt(op1, !DiscardLeft);
      if (ClipperLib.FPoint.op_Inequality(op1b.Pt, Pt))
      {
        op1 = op1b;
        //op1.Pt = Pt;
        op1.Pt.X = Pt.X;
        op1.Pt.Y = Pt.Y;

        op1b = this.DupOutPt(op1, !DiscardLeft);
      }
    }
    else
    {
      while (op1.Next.Pt.X >= Pt.X &&
        op1.Next.Pt.X <= op1.Pt.X && op1.Next.Pt.Y == Pt.Y)
        op1 = op1.Next;
      if (!DiscardLeft && (op1.Pt.X != Pt.X))
        op1 = op1.Next;
      op1b = this.DupOutPt(op1, DiscardLeft);
      if (ClipperLib.FPoint.op_Inequality(op1b.Pt, Pt))
      {
        op1 = op1b;
        //op1.Pt = Pt;
        op1.Pt.X = Pt.X;
        op1.Pt.Y = Pt.Y;

        op1b = this.DupOutPt(op1, DiscardLeft);
      }
    }
    if (Dir2 == ClipperLib.Direction.dLeftToRight)
    {
      while (op2.Next.Pt.X <= Pt.X &&
        op2.Next.Pt.X >= op2.Pt.X && op2.Next.Pt.Y == Pt.Y)
        op2 = op2.Next;
      if (DiscardLeft && (op2.Pt.X != Pt.X))
        op2 = op2.Next;
      op2b = this.DupOutPt(op2, !DiscardLeft);
      if (ClipperLib.FPoint.op_Inequality(op2b.Pt, Pt))
      {
        op2 = op2b;
        //op2.Pt = Pt;
        op2.Pt.X = Pt.X;
        op2.Pt.Y = Pt.Y;

        op2b = this.DupOutPt(op2, !DiscardLeft);
      }
    }
    else
    {
      while (op2.Next.Pt.X >= Pt.X &&
        op2.Next.Pt.X <= op2.Pt.X && op2.Next.Pt.Y == Pt.Y)
        op2 = op2.Next;
      if (!DiscardLeft && (op2.Pt.X != Pt.X))
        op2 = op2.Next;
      op2b = this.DupOutPt(op2, DiscardLeft);
      if (ClipperLib.FPoint.op_Inequality(op2b.Pt, Pt))
      {
        op2 = op2b;
        //op2.Pt = Pt;
        op2.Pt.X = Pt.X;
        op2.Pt.Y = Pt.Y;

        op2b = this.DupOutPt(op2, DiscardLeft);
      }
    }
    if ((Dir1 == ClipperLib.Direction.dLeftToRight) == DiscardLeft)
    {
      op1.Prev = op2;
      op2.Next = op1;
      op1b.Next = op2b;
      op2b.Prev = op1b;
    }
    else
    {
      op1.Next = op2;
      op2.Prev = op1;
      op1b.Prev = op2b;
      op2b.Next = op1b;
    }
    return true;
  };

  ClipperLib.Clipper.prototype.JoinPoints = function (j, outRec1, outRec2)
  {
    var op1 = j.OutPt1,
      op1b;
    var op2 = j.OutPt2,
      op2b;
    //There are 3 kinds of joins for output polygons ...
    //1. Horizontal joins where Join.OutPt1 & Join.OutPt2 are a vertices anywhere
    //along (horizontal) collinear edges (& Join.OffPt is on the same horizontal).
    //2. Non-horizontal joins where Join.OutPt1 & Join.OutPt2 are at the same
    //location at the Bottom of the overlapping segment (& Join.OffPt is above).
    //3. StrictlySimple joins where edges touch but are not collinear and where
    //Join.OutPt1, Join.OutPt2 & Join.OffPt all share the same point.
    var isHorizontal = (j.OutPt1.Pt.Y == j.OffPt.Y);
    if (isHorizontal && (ClipperLib.FPoint.op_Equality(j.OffPt, j.OutPt1.Pt)) && (ClipperLib.FPoint.op_Equality(j.OffPt, j.OutPt2.Pt)))
    {
      //Strictly Simple join ...
      if (outRec1 != outRec2) return false; //ADD THIS LINE
      op1b = j.OutPt1.Next;
      while (op1b != op1 && (ClipperLib.FPoint.op_Equality(op1b.Pt, j.OffPt)))
        op1b = op1b.Next;
      var reverse1 = (op1b.Pt.Y > j.OffPt.Y);
      op2b = j.OutPt2.Next;
      while (op2b != op2 && (ClipperLib.FPoint.op_Equality(op2b.Pt, j.OffPt)))
        op2b = op2b.Next;
      var reverse2 = (op2b.Pt.Y > j.OffPt.Y);
      if (reverse1 == reverse2)
        return false;
      if (reverse1)
      {
        op1b = this.DupOutPt(op1, false);
        op2b = this.DupOutPt(op2, true);
        op1.Prev = op2;
        op2.Next = op1;
        op1b.Next = op2b;
        op2b.Prev = op1b;
        j.OutPt1 = op1;
        j.OutPt2 = op1b;
        return true;
      }
      else
      {
        op1b = this.DupOutPt(op1, true);
        op2b = this.DupOutPt(op2, false);
        op1.Next = op2;
        op2.Prev = op1;
        op1b.Prev = op2b;
        op2b.Next = op1b;
        j.OutPt1 = op1;
        j.OutPt2 = op1b;
        return true;
      }
    }
    else if (isHorizontal)
    {
      //treat horizontal joins differently to non-horizontal joins since with
      //them we're not yet sure where the overlapping is. OutPt1.Pt & OutPt2.Pt
      //may be anywhere along the horizontal edge.
      op1b = op1;
      while (op1.Prev.Pt.Y == op1.Pt.Y && op1.Prev != op1b && op1.Prev != op2)
        op1 = op1.Prev;
      while (op1b.Next.Pt.Y == op1b.Pt.Y && op1b.Next != op1 && op1b.Next != op2)
        op1b = op1b.Next;
      if (op1b.Next == op1 || op1b.Next == op2)
        return false;
      //a flat 'polygon'
      op2b = op2;
      while (op2.Prev.Pt.Y == op2.Pt.Y && op2.Prev != op2b && op2.Prev != op1b)
        op2 = op2.Prev;
      while (op2b.Next.Pt.Y == op2b.Pt.Y && op2b.Next != op2 && op2b.Next != op1)
        op2b = op2b.Next;
      if (op2b.Next == op2 || op2b.Next == op1)
        return false;
      //a flat 'polygon'
      //Op1 -. Op1b & Op2 -. Op2b are the extremites of the horizontal edges

      var $val = {
        Left: null,
        Right: null
      };
      if (!this.GetOverlap(op1.Pt.X, op1b.Pt.X, op2.Pt.X, op2b.Pt.X, $val))
        return false;
      var Left = $val.Left;
      var Right = $val.Right;

      //DiscardLeftSide: when overlapping edges are joined, a spike will created
      //which needs to be cleaned up. However, we don't want Op1 or Op2 caught up
      //on the discard Side as either may still be needed for other joins ...
      var Pt = new ClipperLib.FPoint();
      var DiscardLeftSide;
      if (op1.Pt.X >= Left && op1.Pt.X <= Right)
      {
        //Pt = op1.Pt;
        Pt.X = op1.Pt.X;
        Pt.Y = op1.Pt.Y;
        DiscardLeftSide = (op1.Pt.X > op1b.Pt.X);
      }
      else if (op2.Pt.X >= Left && op2.Pt.X <= Right)
      {
        //Pt = op2.Pt;
        Pt.X = op2.Pt.X;
        Pt.Y = op2.Pt.Y;

        DiscardLeftSide = (op2.Pt.X > op2b.Pt.X);
      }
      else if (op1b.Pt.X >= Left && op1b.Pt.X <= Right)
      {
        //Pt = op1b.Pt;
        Pt.X = op1b.Pt.X;
        Pt.Y = op1b.Pt.Y;

        DiscardLeftSide = op1b.Pt.X > op1.Pt.X;
      }
      else
      {
        //Pt = op2b.Pt;
        Pt.X = op2b.Pt.X;
        Pt.Y = op2b.Pt.Y;

        DiscardLeftSide = (op2b.Pt.X > op2.Pt.X);
      }
      j.OutPt1 = op1;
      j.OutPt2 = op2;
      return this.JoinHorz(op1, op1b, op2, op2b, Pt, DiscardLeftSide);
    }
    else
    {
      //nb: For non-horizontal joins ...
      //    1. Jr.OutPt1.Pt.Y == Jr.OutPt2.Pt.Y
      //    2. Jr.OutPt1.Pt > Jr.OffPt.Y
      //make sure the polygons are correctly oriented ...
      op1b = op1.Next;
      while ((ClipperLib.FPoint.op_Equality(op1b.Pt, op1.Pt)) && (op1b != op1))
        op1b = op1b.Next;
      var Reverse1 = ((op1b.Pt.Y > op1.Pt.Y) || !ClipperLib.ClipperBase.SlopesEqual(op1.Pt, op1b.Pt, j.OffPt));
      if (Reverse1)
      {
        op1b = op1.Prev;
        while ((ClipperLib.FPoint.op_Equality(op1b.Pt, op1.Pt)) && (op1b != op1))
          op1b = op1b.Prev;
        if ((op1b.Pt.Y > op1.Pt.Y) || !ClipperLib.ClipperBase.SlopesEqual(op1.Pt, op1b.Pt, j.OffPt))
          return false;
      }
      op2b = op2.Next;
      while ((ClipperLib.FPoint.op_Equality(op2b.Pt, op2.Pt)) && (op2b != op2))
        op2b = op2b.Next;
      var Reverse2 = ((op2b.Pt.Y > op2.Pt.Y) || !ClipperLib.ClipperBase.SlopesEqual(op2.Pt, op2b.Pt, j.OffPt));
      if (Reverse2)
      {
        op2b = op2.Prev;
        while ((ClipperLib.FPoint.op_Equality(op2b.Pt, op2.Pt)) && (op2b != op2))
          op2b = op2b.Prev;
        if ((op2b.Pt.Y > op2.Pt.Y) || !ClipperLib.ClipperBase.SlopesEqual(op2.Pt, op2b.Pt, j.OffPt))
          return false;
      }
      if ((op1b == op1) || (op2b == op2) || (op1b == op2b) ||
        ((outRec1 == outRec2) && (Reverse1 == Reverse2)))
        return false;
      if (Reverse1)
      {
        op1b = this.DupOutPt(op1, false);
        op2b = this.DupOutPt(op2, true);
        op1.Prev = op2;
        op2.Next = op1;
        op1b.Next = op2b;
        op2b.Prev = op1b;
        j.OutPt1 = op1;
        j.OutPt2 = op1b;
        return true;
      }
      else
      {
        op1b = this.DupOutPt(op1, true);
        op2b = this.DupOutPt(op2, false);
        op1.Next = op2;
        op2.Prev = op1;
        op1b.Prev = op2b;
        op2b.Next = op1b;
        j.OutPt1 = op1;
        j.OutPt2 = op1b;
        return true;
      }
    }
  };

  ClipperLib.Clipper.PointInPolygon = function (pt, path)
  {
    //returns 0 if false, +1 if true, -1 if pt ON polygon boundary
    //http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.88.5498&rep=rep1&type=pdf
    var result = 0,
      cnt = path.length;
    if (cnt < 3)
      return 0;
    var ip = path[0];
    for (var i = 1; i <= cnt; ++i)
    {
      var ipNext = (i == cnt ? path[0] : path[i]);
      if (ClipperLib.Global.IsAlmostEqual(ipNext.Y, pt.Y))
      {
        if ((ClipperLib.Global.IsAlmostEqual(ipNext.X, pt.X)) 
        || (ClipperLib.Global.IsAlmostEqual(ip.Y, pt.Y) 
        && ((ipNext.X > pt.X) == (ip.X < pt.X))))
          return -1;
      }
      if ((ip.Y < pt.Y) != (ipNext.Y < pt.Y))
      {
        if (ip.X >= pt.X)
        {
          if (ipNext.X > pt.X)
            result = 1 - result;
          else
          {
            var d = (ip.X - pt.X) * (ipNext.Y - pt.Y) - (ipNext.X - pt.X) * (ip.Y - pt.Y);
            if (ClipperLib.Global.IsAlmostEqual(d, 0))
              return -1;
            else if ((d > 0) == (ipNext.Y > ip.Y))
              result = 1 - result;
          }
        }
        else
        {
          if (ipNext.X > pt.X)
          {
            var d = (ip.X - pt.X) * (ipNext.Y - pt.Y) - (ipNext.X - pt.X) * (ip.Y - pt.Y);
            if (ClipperLib.Global.IsAlmostEqual(d, 0))
              return -1;
            else if ((d > 0) == (ipNext.Y > ip.Y))
              result = 1 - result;
          }
        }
      }
      ip = ipNext;
    }
    return result;
  };

  ClipperLib.Clipper.prototype.PointInPolygon = function (pt, op)
  {
    //returns 0 if false, +1 if true, -1 if pt ON polygon boundary
    //http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.88.5498&rep=rep1&type=pdf
    var result = 0;
    var startOp = op;
    for (;;)
    {
      var poly0x = op.Pt.X,
        poly0y = op.Pt.Y;
      var poly1x = op.Next.Pt.X,
        poly1y = op.Next.Pt.Y;

/*
        if (poly1y == pt.Y)
        {
            if ((poly1x == pt.X) || (poly0y == pt.Y && ((poly1x > pt.X) == (poly0x < pt.X))))
                return -1;
        }
*/
      if (ClipperLib.Global.IsAlmostEqual(poly1y, pt.Y))
      {
        if (ClipperLib.Global.IsAlmostEqual(poly1x, pt.X) || (ClipperLib.Global.IsAlmostEqual(poly0y, pt.Y) &&
          ((poly1x > pt.X) == (poly0x < pt.X)))) return -1;
      }

      if ((poly0y < pt.Y) != (poly1y < pt.Y))
      {
        if (poly0x >= pt.X)
        {
          if (poly1x > pt.X)
            result = 1 - result;
          else
          {
            var d = (poly0x - pt.X) * (poly1y - pt.Y) - (poly1x - pt.X) * (poly0y - pt.Y);
            if (ClipperLib.Global.IsAlmostEqual(d, 0)) return -1;
            if ((d > 0) == (poly1y > poly0y))
              result = 1 - result;
          }
        }
        else
        {
          if (poly1x > pt.X)
          {
            var d = (poly0x - pt.X) * (poly1y - pt.Y) - (poly1x - pt.X) * (poly0y - pt.Y);
            if (ClipperLib.Global.IsAlmostEqual(d, 0)) return -1;
            if ((d > 0) == (poly1y > poly0y))
              result = 1 - result;
          }
        }
      }
      op = op.Next;
      if (startOp == op)
        break;
    }
    return result;
  };

  ClipperLib.Clipper.prototype.Poly2ContainsPoly1 = function (outPt1, outPt2)
  {
    var op = outPt1;
    do {
      var res = this.PointInPolygon(op.Pt, outPt2);
      if (res >= 0)
        return res != 0;
      op = op.Next;
    }
    while (op != outPt1)
    return true;
  };

  ClipperLib.Clipper.prototype.FixupFirstLefts1 = function (OldOutRec, NewOutRec)
  {
    for (var i = 0, ilen = this.m_PolyOuts.length; i < ilen; i++)
    {
      var outRec = this.m_PolyOuts[i];
      if (outRec.Pts !== null && outRec.FirstLeft == OldOutRec)
      {
        if (this.Poly2ContainsPoly1(outRec.Pts, NewOutRec.Pts))
          outRec.FirstLeft = NewOutRec;
      }
    }
  };

  ClipperLib.Clipper.prototype.FixupFirstLefts2 = function (OldOutRec, NewOutRec)
  {
    for (var $i2 = 0, $t2 = this.m_PolyOuts, $l2 = $t2.length, outRec = $t2[$i2]; $i2 < $l2; $i2++, outRec = $t2[$i2])
      if (outRec.FirstLeft == OldOutRec)
        outRec.FirstLeft = NewOutRec;
  };

  ClipperLib.Clipper.ParseFirstLeft = function (FirstLeft)
  {
    while (FirstLeft != null && FirstLeft.Pts == null)
      FirstLeft = FirstLeft.FirstLeft;
    return FirstLeft;
  };

  ClipperLib.Clipper.prototype.JoinCommonEdges = function ()
  {
    for (var i = 0; i < this.m_Joins.length; i++)
    {
      var join = this.m_Joins[i];
      var outRec1 = this.GetOutRec(join.OutPt1.Idx);
      var outRec2 = this.GetOutRec(join.OutPt2.Idx);
      if (outRec1.Pts == null || outRec2.Pts == null)
        continue;
      //get the polygon fragment with the correct hole state (FirstLeft)
      //before calling JoinPoints() ...
      var holeStateRec;
      if (outRec1 == outRec2)
        holeStateRec = outRec1;
      else if (this.Param1RightOfParam2(outRec1, outRec2))
        holeStateRec = outRec2;
      else if (this.Param1RightOfParam2(outRec2, outRec1))
        holeStateRec = outRec1;
      else
        holeStateRec = this.GetLowermostRec(outRec1, outRec2);
      if (!this.JoinPoints(join, outRec1, outRec2))
        continue;
      if (outRec1 == outRec2)
      {
        //instead of joining two polygons, we've just created a new one by
        //splitting one polygon into two.
        outRec1.Pts = join.OutPt1;
        outRec1.BottomPt = null;
        outRec2 = this.CreateOutRec();
        outRec2.Pts = join.OutPt2;
        //update all OutRec2.Pts Idx's ...
        this.UpdateOutPtIdxs(outRec2);
        //We now need to check every OutRec.FirstLeft pointer. If it points
        //to OutRec1 it may need to point to OutRec2 instead ...
        if (this.m_UsingPolyTree)
          for (var j = 0; j < this.m_PolyOuts.length - 1; j++)
          {
            var oRec = this.m_PolyOuts[j];
            if (oRec.Pts == null || ClipperLib.Clipper.ParseFirstLeft(oRec.FirstLeft) != outRec1 || oRec.IsHole == outRec1.IsHole)
              continue;
            if (this.Poly2ContainsPoly1(oRec.Pts, join.OutPt2))
              oRec.FirstLeft = outRec2;
          }
        if (this.Poly2ContainsPoly1(outRec2.Pts, outRec1.Pts))
        {
          //outRec2 is contained by outRec1 ...
          outRec2.IsHole = !outRec1.IsHole;
          outRec2.FirstLeft = outRec1;
          //fixup FirstLeft pointers that may need reassigning to OutRec1
          if (this.m_UsingPolyTree)
            this.FixupFirstLefts2(outRec2, outRec1);
          if ((outRec2.IsHole ^ this.ReverseSolution) == (this.Area(outRec2) > 0))
            this.ReversePolyPtLinks(outRec2.Pts);
        }
        else if (this.Poly2ContainsPoly1(outRec1.Pts, outRec2.Pts))
        {
          //outRec1 is contained by outRec2 ...
          outRec2.IsHole = outRec1.IsHole;
          outRec1.IsHole = !outRec2.IsHole;
          outRec2.FirstLeft = outRec1.FirstLeft;
          outRec1.FirstLeft = outRec2;
          //fixup FirstLeft pointers that may need reassigning to OutRec1
          if (this.m_UsingPolyTree)
            this.FixupFirstLefts2(outRec1, outRec2);
          if ((outRec1.IsHole ^ this.ReverseSolution) == (this.Area(outRec1) > 0))
            this.ReversePolyPtLinks(outRec1.Pts);
        }
        else
        {
          //the 2 polygons are completely separate ...
          outRec2.IsHole = outRec1.IsHole;
          outRec2.FirstLeft = outRec1.FirstLeft;
          //fixup FirstLeft pointers that may need reassigning to OutRec2
          if (this.m_UsingPolyTree)
            this.FixupFirstLefts1(outRec1, outRec2);
        }
      }
      else
      {
        //joined 2 polygons together ...
        outRec2.Pts = null;
        outRec2.BottomPt = null;
        outRec2.Idx = outRec1.Idx;
        outRec1.IsHole = holeStateRec.IsHole;
        if (holeStateRec == outRec2)
          outRec1.FirstLeft = outRec2.FirstLeft;
        outRec2.FirstLeft = outRec1;
        //fixup FirstLeft pointers that may need reassigning to OutRec1
        if (this.m_UsingPolyTree)
          this.FixupFirstLefts2(outRec2, outRec1);
      }
    }
  };

  ClipperLib.Clipper.prototype.UpdateOutPtIdxs = function (outrec)
  {
    var op = outrec.Pts;
    do {
      op.Idx = outrec.Idx;
      op = op.Prev;
    }
    while (op != outrec.Pts)
  };

  ClipperLib.Clipper.prototype.DoSimplePolygons = function ()
  {
    var i = 0;
    while (i < this.m_PolyOuts.length)
    {
      var outrec = this.m_PolyOuts[i++];
      var op = outrec.Pts;
      if (op === null)
        continue;
      do //for each Pt in Polygon until duplicate found do ...
      {
        var op2 = op.Next;
        while (op2 != outrec.Pts)
        {
          if ((ClipperLib.FPoint.op_Equality(op.Pt, op2.Pt)) && op2.Next != op && op2.Prev != op)
          {
            //split the polygon into two ...
            var op3 = op.Prev;
            var op4 = op2.Prev;
            op.Prev = op4;
            op4.Next = op;
            op2.Prev = op3;
            op3.Next = op2;

            outrec.Pts = op;
            var outrec2 = this.CreateOutRec();
            outrec2.Pts = op2;
            this.UpdateOutPtIdxs(outrec2);
            if (this.Poly2ContainsPoly1(outrec2.Pts, outrec.Pts))
            {
              //OutRec2 is contained by OutRec1 ...
              outrec2.IsHole = !outrec.IsHole;
              outrec2.FirstLeft = outrec;
            }
            else if (this.Poly2ContainsPoly1(outrec.Pts, outrec2.Pts))
            {
              //OutRec1 is contained by OutRec2 ...
              outrec2.IsHole = outrec.IsHole;
              outrec.IsHole = !outrec2.IsHole;
              outrec2.FirstLeft = outrec.FirstLeft;
              outrec.FirstLeft = outrec2;
            }
            else
            {
              //the 2 polygons are separate ...
              outrec2.IsHole = outrec.IsHole;
              outrec2.FirstLeft = outrec.FirstLeft;
            }
            op2 = op;
            //ie get ready for the next iteration
          }
          op2 = op2.Next;
        }
        op = op.Next;
      }
      while (op != outrec.Pts)
    }
  };

  ClipperLib.Clipper.Area = function (poly)
  {
    var cnt = poly.length;
    if (cnt < 3)
      return 0;
    var a = 0;
    for (var i = 0, j = cnt - 1; i < cnt; ++i)
    {
      a += (poly[j].X + poly[i].X) * (poly[j].Y - poly[i].Y);
      j = i;
    }
    return -a * 0.5;
  };
  ClipperLib.Clipper.prototype.Area = function (outRec)
  {
    var op = outRec.Pts;
    if (op == null)
      return 0;
    var a = 0;
    do {
      a = a + (op.Prev.Pt.X + op.Pt.X) * (op.Prev.Pt.Y - op.Pt.Y);
      op = op.Next;
    }
    while (op != outRec.Pts)
    return a * 0.5;
  };

  //------------------------------------------------------------------------------
  // OffsetPolygon functions ...
  //------------------------------------------------------------------------------

  if (use_deprecated)
  {
    ClipperLib.Clipper.OffsetPaths = function (polys, delta, jointype, endtype, MiterLimit)
    {
      var result = new ClipperLib.Paths();
      var co = new ClipperLib.ClipperOffset(MiterLimit, MiterLimit);
      co.AddPaths(polys, jointype, endtype);
      co.Execute(result, delta);
      return result;
    };
  }

  if (use_deprecated)
  {
    ClipperLib.Clipper.OffsetPaths = ClipperLib.Clipper.prototype.OffsetPaths = function (polys, delta, jointype, endtype, MiterLimit)
    {
      var result = new Array();
      var co = new ClipperLib.ClipperOffset(jointype, endtype, MiterLimit, MiterLimit);
      co.AddPaths(polys, endtype == ClipperLib.EndType.etClosed);
      co.Execute(result, delta);
      return result;
    };
  }

  ClipperLib.Clipper.SimplifyPolygon = function (poly, fillType)
  {
    var result = new Array();
    var c = new ClipperLib.Clipper(0);
    c.StrictlySimple = true;
    c.AddPath(poly, ClipperLib.PolyType.ptSubject, true);
    c.Execute(ClipperLib.ClipType.ctUnion, result, fillType, fillType);
    return result;
  };

  ClipperLib.Clipper.SimplifyPolygons = function (polys, fillType)
  {
    var result = new Array();
    var c = new ClipperLib.Clipper(0);
    c.StrictlySimple = true;
    c.AddPaths(polys, ClipperLib.PolyType.ptSubject, true);
    c.Execute(ClipperLib.ClipType.ctUnion, result, fillType, fillType);
    return result;
  };

  ClipperLib.Clipper.DistanceSqrd = function (pt1, pt2)
  {
    var dx = (pt1.X - pt2.X);
    var dy = (pt1.Y - pt2.Y);
    return (dx * dx + dy * dy);
  };

  ClipperLib.Clipper.DistanceFromLineSqrd = function (pt, ln1, ln2)
  {
    //The equation of a line in general form (Ax + By + C = 0)
    //given 2 points (x¹,y¹) & (x²,y²) is ...
    //(y¹ - y²)x + (x² - x¹)y + (y² - y¹)x¹ - (x² - x¹)y¹ = 0
    //A = (y¹ - y²); B = (x² - x¹); C = (y² - y¹)x¹ - (x² - x¹)y¹
    //perpendicular distance of point (x³,y³) = (Ax³ + By³ + C)/Sqrt(A² + B²)
    //see http://en.wikipedia.org/wiki/Perpendicular_distance
    var A = ln1.Y - ln2.Y;
    var B = ln2.X - ln1.X;
    var C = A * ln1.X + B * ln1.Y;
    C = A * pt.X + B * pt.Y - C;
    return (C * C) / (A * A + B * B);
  };

  ClipperLib.Clipper.SlopesNearCollinear = function (pt1, pt2, pt3, distSqrd)
  {
    return ClipperLib.Clipper.DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd;
  };

  ClipperLib.Clipper.PointsAreClose = function (pt1, pt2, distSqrd)
  {
    var dx = pt1.X - pt2.X;
    var dy = pt1.Y - pt2.Y;
    return ((dx * dx) + (dy * dy) <= distSqrd);
  };

  //------------------------------------------------------------------------------
  ClipperLib.Clipper.ExcludeOp = function (op)
  {
    var result = op.Prev;
    result.Next = op.Next;
    op.Next.Prev = result;
    result.Idx = 0;
    return result;
  };
  ClipperLib.Clipper.CleanPolygon = function (path, distance)
  {
    //distance = proximity in units/pixels below which vertices will be stripped. 
    //Default ~= sqrt(2) so when adjacent vertices or semi-adjacent vertices have 
    //both x & y coords within 1 unit, then the second vertex will be stripped.
    var cnt = path.length;
    if (cnt == 0)
      return new Array();
    var outPts = new Array(cnt);
    for (var i = 0; i < cnt; ++i)
      outPts[i] = new ClipperLib.OutPt();
    for (var i = 0; i < cnt; ++i)
    {
      outPts[i].Pt = path[i];
      outPts[i].Next = outPts[(i + 1) % cnt];
      outPts[i].Next.Prev = outPts[i];
      outPts[i].Idx = 0;
    }
    var distSqrd = distance * distance;
    var op = outPts[0];
    while (op.Idx == 0 && op.Next != op.Prev)
    {
      if (ClipperLib.Clipper.PointsAreClose(op.Pt, op.Prev.Pt, distSqrd))
      {
        op = ClipperLib.Clipper.ExcludeOp(op);
        cnt--;
      }
      else if (ClipperLib.Clipper.PointsAreClose(op.Prev.Pt, op.Next.Pt, distSqrd))
      {
        ClipperLib.Clipper.ExcludeOp(op.Next);
        op = ClipperLib.Clipper.ExcludeOp(op);
        cnt -= 2;
      }
      else if (ClipperLib.Clipper.SlopesNearCollinear(op.Prev.Pt, op.Pt, op.Next.Pt, distSqrd))
      {
        op = ClipperLib.Clipper.ExcludeOp(op);
        cnt--;
      }
      else
      {
        op.Idx = 1;
        op = op.Next;
      }
    }
    if (cnt < 3)
      cnt = 0;
    var result = new Array(cnt);
    for (var i = 0; i < cnt; ++i)
    {
      result[i] = op.Pt;
      op = op.Next;
    }
    outPts = null;
    return result;
  };
  ClipperLib.Clipper.CleanPolygons = function (polys, distance)
  {
    var result = new Array(polys.length);
    for (var i = 0, ilen = polys.length; i < ilen; i++)
      result[i] = ClipperLib.Clipper.CleanPolygon(polys[i], distance);
    return result;
  };

  ClipperLib.Clipper.Minkowski = function (poly, path, IsSum, IsClosed)
  {
    var delta = (IsClosed ? 1 : 0);
    var polyCnt = poly.length;
    var pathCnt = path.length;
    var result = new Array();
    if (IsSum)
      for (var i = 0; i < pathCnt; i++)
      {
        var p = new Array(polyCnt);
        for (var j = 0, jlen = poly.length, ip = poly[j]; j < jlen; j++, ip = poly[j])
          p[j] = new ClipperLib.FPoint(path[i].X + ip.X, path[i].Y + ip.Y);
        result.push(p);
      }
    else
      for (var i = 0; i < pathCnt; i++)
      {
        var p = new Array(polyCnt);
        for (var j = 0, jlen = poly.length, ip = poly[j]; j < jlen; j++, ip = poly[j])
          p[j] = new ClipperLib.FPoint(path[i].X - ip.X, path[i].Y - ip.Y);
        result.push(p);
      }
    var quads = new Array();
    for (var i = 0; i <= pathCnt - 2 + delta; i++)
      for (var j = 0; j <= polyCnt - 1; j++)
      {
        var quad = new Array();
        quad.push(result[i % pathCnt][j % polyCnt]);
        quad.push(result[(i + 1) % pathCnt][j % polyCnt]);
        quad.push(result[(i + 1) % pathCnt][(j + 1) % polyCnt]);
        quad.push(result[i % pathCnt][(j + 1) % polyCnt]);
        if (!ClipperLib.Clipper.Orientation(quad))
          quad.reverse();
        quads.push(quad);
      }
    var c = new ClipperLib.Clipper(0);
    c.AddPaths(quads, ClipperLib.PolyType.ptSubject, true);
    c.Execute(ClipperLib.ClipType.ctUnion, result, ClipperLib.PolyFillType.pftNonZero, ClipperLib.PolyFillType.pftNonZero);
    return result;
  };
  ClipperLib.Clipper.MinkowskiSum = function (poly, path, IsClosed)
  {
    return ClipperLib.Clipper.Minkowski(poly, path, true, IsClosed);
  };
  ClipperLib.Clipper.MinkowskiDiff = function (poly, path, IsClosed)
  {
    return ClipperLib.Clipper.Minkowski(poly, path, false, IsClosed);
  };

  ClipperLib.Clipper.PolyTreeToPaths = function (polytree)
  {
    var result = new Array();
    //result.set_Capacity(polytree.get_Total());
    ClipperLib.Clipper.AddPolyNodeToPaths(polytree, ClipperLib.Clipper.NodeType.ntAny, result);
    return result;
  };
  ClipperLib.Clipper.AddPolyNodeToPaths = function (polynode, nt, paths)
  {
    var match = true;
    switch (nt)
    {
    case ClipperLib.Clipper.NodeType.ntOpen:
      return;
    case ClipperLib.Clipper.NodeType.ntClosed:
      match = !polynode.IsOpen;
      break;
    default:
      break;
    }

    if (polynode.m_polygon.length > 0 && match)
      paths.push(polynode.m_polygon);
    for (var $i3 = 0, $t3 = polynode.Childs(), $l3 = $t3.length, pn = $t3[$i3]; $i3 < $l3; $i3++, pn = $t3[$i3])
      ClipperLib.Clipper.AddPolyNodeToPaths(pn, nt, paths);
  };

  ClipperLib.Clipper.OpenPathsFromPolyTree = function (polytree)
  {
    var result = new ClipperLib.Paths();
    //result.set_Capacity(polytree.ChildCount());
    for (var i = 0, ilen = polytree.ChildCount(); i < ilen; i++)
      if (polytree.Childs()[i].IsOpen)
        result.push(polytree.Childs()[i].m_polygon);
    return result;
  };
  ClipperLib.Clipper.ClosedPathsFromPolyTree = function (polytree)
  {
    var result = new ClipperLib.Paths();
    //result.set_Capacity(polytree.Total());
    ClipperLib.Clipper.AddPolyNodeToPaths(polytree, ClipperLib.Clipper.NodeType.ntClosed, result);
    return result;
  };

  Inherit(ClipperLib.Clipper, ClipperLib.ClipperBase);


  ClipperLib.Clipper.NodeType = {
    ntAny: 0,
    ntOpen: 1,
    ntClosed: 2
  };

  ClipperLib.ClipperOffset = function (miterLimit, arcTolerance)
  {
    if (typeof (miterLimit) == "undefined") miterLimit = 2;
    if (typeof (arcTolerance) == "undefined") arcTolerance = ClipperLib.ClipperOffset.def_arc_tolerance;

    this.m_destPolys = new ClipperLib.Paths();
    this.m_srcPoly = new ClipperLib.Path();
    this.m_destPoly = new ClipperLib.Path();
    this.m_normals = new Array();
    this.m_delta = 0;
    this.m_sinA = 0;
    this.m_sin = 0;
    this.m_cos = 0;
    this.m_miterLim = 0;
    this.m_StepsPerRad = 0;
    this.m_lowest = new ClipperLib.FPoint();
    this.m_polyNodes = new ClipperLib.PolyNode();
    this.MiterLimit = miterLimit;
    this.ArcTolerance = arcTolerance;
    this.m_lowest.X = -1;
  };
  ClipperLib.ClipperOffset.two_pi = 6.28318530717959;
  ClipperLib.ClipperOffset.def_arc_tolerance = 0.25;
  ClipperLib.ClipperOffset.prototype.Clear = function ()
  {
    ClipperLib.Clear(this.m_polyNodes.Childs());
    this.m_lowest.X = -1;
  };
  ClipperLib.ClipperOffset.Round = function (value)
  {
    return value < 0 ? (value - 0.5) : (value + 0.5);
  };
  ClipperLib.ClipperOffset.prototype.AddPath = function (path, joinType, endType)
  {
    var highI = path.length - 1;
    if (highI < 0) return;
    var newNode = new ClipperLib.PolyNode();
    newNode.m_jointype = joinType;
    newNode.m_endtype = endType;

    //strip duplicate points from path and also get index to the lowest point ...
    if (endType == ClipperLib.EndType.etClosedLine || endType == ClipperLib.EndType.etClosedPolygon)
      while (highI > 0 && ClipperLib.FPoint.op_Equality(path[0], path[highI]))
        highI--;
    // newNode.m_polygon.set_Capacity(highI + 1);
    if(use_xyz && path[0].Z !== void 0)
    	newNode.m_polygon.push(new ClipperLib.FPoint(path[0].X, path[0].Y, path[0].Z));
		else
    	newNode.m_polygon.push(new ClipperLib.FPoint(path[0].X, path[0].Y));
		
    var j = 0,
      k = 0;
    for (var i = 1; i <= highI; i++)
      if (ClipperLib.FPoint.op_Inequality(newNode.m_polygon[j], path[i]))
      {
        j++;
        
        if(use_xyz && path[i].Z !== void 0)
        	newNode.m_polygon.push(new ClipperLib.FPoint(path[i].X, path[i].Y, path[i].Z));
        else
        	newNode.m_polygon.push(new ClipperLib.FPoint(path[i].X, path[i].Y));
        
        if (path[i].Y > newNode.m_polygon[k].Y ||
          (path[i].Y == newNode.m_polygon[k].Y &&
            path[i].X < newNode.m_polygon[k].X))
          k = j;
      }
    if ((endType == ClipperLib.EndType.etClosedPolygon && j < 2) || (endType != ClipperLib.EndType.etClosedPolygon && j < 0))
      return;
    this.m_polyNodes.AddChild(newNode);
    //if this path's lowest pt is lower than all the others then update m_lowest
    if (endType != ClipperLib.EndType.etClosedPolygon)
      return;
    if (this.m_lowest.X < 0)
      this.m_lowest = new ClipperLib.FPoint(0, k);
    else
    {
      var ip = this.m_polyNodes.Childs()[ClipperLib.Cast_Int32(this.m_lowest.X)].m_polygon[ClipperLib.Cast_Int32(this.m_lowest.Y)];
      if (newNode.m_polygon[k].Y > ip.Y || (newNode.m_polygon[k].Y == ip.Y && newNode.m_polygon[k].X < ip.X))
        this.m_lowest = new ClipperLib.FPoint(this.m_polyNodes.ChildCount() - 1, k);
    }

  };
  ClipperLib.ClipperOffset.prototype.AddPaths = function (paths, joinType, endType)
  {
    for (var i = 0, ilen = paths.length; i < ilen; i++)
      this.AddPath(paths[i], joinType, endType);
  };
  ClipperLib.ClipperOffset.prototype.FixOrientations = function ()
  {
    //fixup orientations of all closed paths if the orientation of the
    //closed path with the lowermost vertex is wrong ...
    if (this.m_lowest.X >= 0 && !ClipperLib.Clipper.Orientation(this.m_polyNodes.Childs()[this.m_lowest.X].m_polygon))
    {
      for (var i = 0; i < this.m_polyNodes.ChildCount(); i++)
      {
        var node = this.m_polyNodes.Childs()[i];
        if (node.m_endtype == ClipperLib.EndType.etClosedPolygon || (node.m_endtype == ClipperLib.EndType.etClosedLine && ClipperLib.Clipper.Orientation(node.m_polygon)))
          node.m_polygon.reverse();
      }
    }
    else
    {
      for (var i = 0; i < this.m_polyNodes.ChildCount(); i++)
      {
        var node = this.m_polyNodes.Childs()[i];
        if (node.m_endtype == ClipperLib.EndType.etClosedLine && !ClipperLib.Clipper.Orientation(node.m_polygon))
          node.m_polygon.reverse();
      }
    }
  };
  ClipperLib.ClipperOffset.GetUnitNormal = function (pt1, pt2)
  {
    var dx = (pt2.X - pt1.X);
    var dy = (pt2.Y - pt1.Y);
    if ((dx == 0) && (dy == 0))
      return new ClipperLib.FPoint(0, 0);
    var f = 1 / Math.sqrt(dx * dx + dy * dy);
    dx *= f;
    dy *= f;
    return new ClipperLib.FPoint(dy, -dx);
  };


  ClipperLib.ClipperOffset.prototype.DoOffset = function (delta)
  {
    this.m_destPolys = new Array();
    this.m_delta = delta;
    var absDelta = Math.abs(delta);

    //if Zero offset, just copy any CLOSED polygons to m_p and return ...
    if (absDelta < 1e-06)
    {
      //this.m_destPolys.set_Capacity(this.m_polyNodes.ChildCount);
      for (var i = 0; i < this.m_polyNodes.ChildCount(); i++)
      {
        var node = this.m_polyNodes.Childs()[i];
        if (node.m_endtype == ClipperLib.EndType.etClosedPolygon)
          this.m_destPolys.push(node.m_polygon);
      }
      return;
    }
    //see offset_triginometry3.svg in the documentation folder ...
    if (this.MiterLimit > 2)
      this.m_miterLim = 2 / (this.MiterLimit * this.MiterLimit);
    else
      this.m_miterLim = 0.5;
    var y;
    if (this.ArcTolerance <= 0)
      y = ClipperLib.ClipperOffset.def_arc_tolerance;
    else if (this.ArcTolerance > absDelta * ClipperLib.ClipperOffset.def_arc_tolerance)
      y = absDelta * ClipperLib.ClipperOffset.def_arc_tolerance;
    else
      y = this.ArcTolerance;
    //see offset_triginometry2.svg in the documentation folder ...
    var steps = 3.14159265358979 / Math.acos(1 - y / absDelta);
    this.m_sin = Math.sin(ClipperLib.ClipperOffset.two_pi / steps);
    this.m_cos = Math.cos(ClipperLib.ClipperOffset.two_pi / steps);
    this.m_StepsPerRad = steps / ClipperLib.ClipperOffset.two_pi;
    if (delta < 0)
      this.m_sin = -this.m_sin;
    //this.m_destPolys.set_Capacity(this.m_polyNodes.ChildCount * 2);
    for (var i = 0; i < this.m_polyNodes.ChildCount(); i++)
    {
      var node = this.m_polyNodes.Childs()[i];
      this.m_srcPoly = node.m_polygon;
      var len = this.m_srcPoly.length;
      if (len == 0 || (delta <= 0 && (len < 3 || node.m_endtype != ClipperLib.EndType.etClosedPolygon)))
        continue;
      this.m_destPoly = new Array();
      if (len == 1)
      {
        if (node.m_jointype == ClipperLib.JoinType.jtRound)
        {
          var X = 1,
            Y = 0;
          for (var j = 1; j <= steps; j++)
          {
            this.m_destPoly.push(new ClipperLib.FPoint(this.m_srcPoly[0].X + X * delta, this.m_srcPoly[0].Y + Y * delta));
            var X2 = X;
            X = X * this.m_cos - this.m_sin * Y;
            Y = X2 * this.m_sin + Y * this.m_cos;
          }
        }
        else
        {
          var X = -1,
            Y = -1;
          for (var j = 0; j < 4; ++j)
          {
            this.m_destPoly.push(new ClipperLib.FPoint(this.m_srcPoly[0].X + X * delta, this.m_srcPoly[0].Y + Y * delta));
            if (X < 0)
              X = 1;
            else if (Y < 0)
              Y = 1;
            else
              X = -1;
          }
        }
        this.m_destPolys.push(this.m_destPoly);
        continue;
      }
      //build m_normals ...
      this.m_normals.length = 0;
      //this.m_normals.set_Capacity(len);
      for (var j = 0; j < len - 1; j++)
        this.m_normals.push(ClipperLib.ClipperOffset.GetUnitNormal(this.m_srcPoly[j], this.m_srcPoly[j + 1]));
      if (node.m_endtype == ClipperLib.EndType.etClosedLine || node.m_endtype == ClipperLib.EndType.etClosedPolygon)
        this.m_normals.push(ClipperLib.ClipperOffset.GetUnitNormal(this.m_srcPoly[len - 1], this.m_srcPoly[0]));
      else
        this.m_normals.push(new ClipperLib.FPoint(this.m_normals[len - 2]));
      if (node.m_endtype == ClipperLib.EndType.etClosedPolygon)
      {
        var k = len - 1;
        for (var j = 0; j < len; j++)
          k = this.OffsetPoint(j, k, node.m_jointype);
        this.m_destPolys.push(this.m_destPoly);
      }
      else if (node.m_endtype == ClipperLib.EndType.etClosedLine)
      {
        var k = len - 1;
        for (var j = 0; j < len; j++)
          k = this.OffsetPoint(j, k, node.m_jointype);
        this.m_destPolys.push(this.m_destPoly);
        this.m_destPoly = new Array();
        //re-build m_normals ...
        var n = this.m_normals[len - 1];
        for (var j = len - 1; j > 0; j--)
          this.m_normals[j] = new ClipperLib.FPoint(-this.m_normals[j - 1].X, -this.m_normals[j - 1].Y);
        this.m_normals[0] = new ClipperLib.FPoint(-n.X, -n.Y);
        k = 0;
        for (var j = len - 1; j >= 0; j--)
          k = this.OffsetPoint(j, k, node.m_jointype);
        this.m_destPolys.push(this.m_destPoly);
      }
      else
      {
        var k = 0;
        for (var j = 1; j < len - 1; ++j)
          k = this.OffsetPoint(j, k, node.m_jointype);
        var pt1;
        if (node.m_endtype == ClipperLib.EndType.etOpenButt)
        {
          var j = len - 1;
          pt1 = new ClipperLib.FPoint(this.m_srcPoly[j].X + this.m_normals[j].X * delta, this.m_srcPoly[j].Y + this.m_normals[j].Y * delta);
          this.m_destPoly.push(pt1);
          pt1 = new ClipperLib.FPoint(this.m_srcPoly[j].X - this.m_normals[j].X * delta, this.m_srcPoly[j].Y - this.m_normals[j].Y * delta);
          this.m_destPoly.push(pt1);
        }
        else
        {
          var j = len - 1;
          k = len - 2;
          this.m_sinA = 0;
          this.m_normals[j] = new ClipperLib.FPoint(-this.m_normals[j].X, -this.m_normals[j].Y);
          if (node.m_endtype == ClipperLib.EndType.etOpenSquare)
            this.DoSquare(j, k);
          else
            this.DoRound(j, k);
        }
        //re-build m_normals ...
        for (var j = len - 1; j > 0; j--)
          this.m_normals[j] = new ClipperLib.FPoint(-this.m_normals[j - 1].X, -this.m_normals[j - 1].Y);
        this.m_normals[0] = new ClipperLib.FPoint(-this.m_normals[1].X, -this.m_normals[1].Y);
        k = len - 1;
        for (var j = k - 1; j > 0; --j)
          k = this.OffsetPoint(j, k, node.m_jointype);
        if (node.m_endtype == ClipperLib.EndType.etOpenButt)
        {
          pt1 = new ClipperLib.FPoint(this.m_srcPoly[0].X - this.m_normals[0].X * delta, this.m_srcPoly[0].Y - this.m_normals[0].Y * delta);
          this.m_destPoly.push(pt1);
          pt1 = new ClipperLib.FPoint(this.m_srcPoly[0].X + this.m_normals[0].X * delta, this.m_srcPoly[0].Y + this.m_normals[0].Y * delta);
          this.m_destPoly.push(pt1);
        }
        else
        {
          k = 1;
          this.m_sinA = 0;
          if (node.m_endtype == ClipperLib.EndType.etOpenSquare)
            this.DoSquare(0, 1);
          else
            this.DoRound(0, 1);
        }
        this.m_destPolys.push(this.m_destPoly);
      }
    }
  };
  ClipperLib.ClipperOffset.prototype.Execute = function ()
  {
    var a = arguments,
      ispolytree = a[0] instanceof ClipperLib.PolyTree;
    if (!ispolytree) // function (solution, delta)
    {
      var solution = a[0],
        delta = a[1];
      ClipperLib.Clear(solution);
      this.FixOrientations();
      this.DoOffset(delta);
      //now clean up 'corners' ...
      var clpr = new ClipperLib.Clipper(0);
      clpr.AddPaths(this.m_destPolys, ClipperLib.PolyType.ptSubject, true);
      if (delta > 0)
      {
        clpr.Execute(ClipperLib.ClipType.ctUnion, solution, ClipperLib.PolyFillType.pftPositive, ClipperLib.PolyFillType.pftPositive);
      }
      else
      {
        var r = ClipperLib.Clipper.GetBounds(this.m_destPolys);
        var outer = new ClipperLib.Path();
        outer.push(new ClipperLib.FPoint(r.left - 10, r.bottom + 10));
        outer.push(new ClipperLib.FPoint(r.right + 10, r.bottom + 10));
        outer.push(new ClipperLib.FPoint(r.right + 10, r.top - 10));
        outer.push(new ClipperLib.FPoint(r.left - 10, r.top - 10));
        clpr.AddPath(outer, ClipperLib.PolyType.ptSubject, true);
        clpr.ReverseSolution = true;
        clpr.Execute(ClipperLib.ClipType.ctUnion, solution, ClipperLib.PolyFillType.pftNegative, ClipperLib.PolyFillType.pftNegative);
        if (solution.length > 0)
          solution.splice(0, 1);
      }
    }
    else // function (polytree, delta)
    {
      var solution = a[0],
        delta = a[1];
      solution.Clear();
      this.FixOrientations();
      this.DoOffset(delta);

      //now clean up 'corners' ...
      var clpr = new ClipperLib.Clipper(0);
      clpr.AddPaths(this.m_destPolys, ClipperLib.PolyType.ptSubject, true);
      if (delta > 0)
      {
        clpr.Execute(ClipperLib.ClipType.ctUnion, solution, ClipperLib.PolyFillType.pftPositive, ClipperLib.PolyFillType.pftPositive);
      }
      else
      {
        var r = ClipperLib.Clipper.GetBounds(this.m_destPolys);
        var outer = new ClipperLib.Path();
        outer.push(new ClipperLib.FPoint(r.left - 10, r.bottom + 10));
        outer.push(new ClipperLib.FPoint(r.right + 10, r.bottom + 10));
        outer.push(new ClipperLib.FPoint(r.right + 10, r.top - 10));
        outer.push(new ClipperLib.FPoint(r.left - 10, r.top - 10));

        clpr.AddPath(outer, ClipperLib.PolyType.ptSubject, true);
        clpr.ReverseSolution = true;
        clpr.Execute(ClipperLib.ClipType.ctUnion, solution, ClipperLib.PolyFillType.pftNegative, ClipperLib.PolyFillType.pftNegative);
        //remove the outer PolyNode rectangle ...
        if (solution.ChildCount() == 1 && solution.Childs()[0].ChildCount() > 0)
        {
          var outerNode = solution.Childs()[0];
          //solution.Childs.set_Capacity(outerNode.ChildCount);
          solution.Childs()[0] = outerNode.Childs()[0];
          for (var i = 1; i < outerNode.ChildCount(); i++)
            solution.AddChild(outerNode.Childs()[i]);
        }
        else
          solution.Clear();
      }

    }
  };

  ClipperLib.ClipperOffset.prototype.OffsetPoint = function (j, k, jointype)
  {
    this.m_sinA = (this.m_normals[k].X * this.m_normals[j].Y - this.m_normals[j].X * this.m_normals[k].Y);
    if (this.m_sinA < 0.00005 && this.m_sinA > -0.00005)
      return k;
    else if (this.m_sinA > 1)
      this.m_sinA = 1.0;
    else if (this.m_sinA < -1)
      this.m_sinA = -1.0;
    if (this.m_sinA * this.m_delta < 0)
    {
      this.m_destPoly.push(new ClipperLib.FPoint(this.m_srcPoly[j].X + this.m_normals[k].X * this.m_delta,
        this.m_srcPoly[j].Y + this.m_normals[k].Y * this.m_delta));
      this.m_destPoly.push(new ClipperLib.FPoint(this.m_srcPoly[j]));
      this.m_destPoly.push(new ClipperLib.FPoint(this.m_srcPoly[j].X + this.m_normals[j].X * this.m_delta,
        this.m_srcPoly[j].Y + this.m_normals[j].Y * this.m_delta));
    }
    else
      switch (jointype)
      {
      case ClipperLib.JoinType.jtMiter:
        {
          var r = 1 + (this.m_normals[j].X * this.m_normals[k].X + this.m_normals[j].Y * this.m_normals[k].Y);
          if (r >= this.m_miterLim)
            this.DoMiter(j, k, r);
          else
            this.DoSquare(j, k);
          break;
        }
      case ClipperLib.JoinType.jtSquare:
        this.DoSquare(j, k);
        break;
      case ClipperLib.JoinType.jtRound:
        this.DoRound(j, k);
        break;
      }
    k = j;
    return k;
  };
  ClipperLib.ClipperOffset.prototype.DoSquare = function (j, k)
  {
    var dx = Math.tan(Math.atan2(this.m_sinA,
      this.m_normals[k].X * this.m_normals[j].X + this.m_normals[k].Y * this.m_normals[j].Y) / 4);
    this.m_destPoly.push(new ClipperLib.FPoint(
      this.m_srcPoly[j].X + this.m_delta * (this.m_normals[k].X - this.m_normals[k].Y * dx),
      this.m_srcPoly[j].Y + this.m_delta * (this.m_normals[k].Y + this.m_normals[k].X * dx)));
    this.m_destPoly.push(new ClipperLib.FPoint(
      this.m_srcPoly[j].X + this.m_delta * (this.m_normals[j].X + this.m_normals[j].Y * dx),
      this.m_srcPoly[j].Y + this.m_delta * (this.m_normals[j].Y - this.m_normals[j].X * dx)));
  };
  ClipperLib.ClipperOffset.prototype.DoMiter = function (j, k, r)
  {
    var q = this.m_delta / r;
    this.m_destPoly.push(new ClipperLib.FPoint(
      this.m_srcPoly[j].X + (this.m_normals[k].X + this.m_normals[j].X) * q,
      this.m_srcPoly[j].Y + (this.m_normals[k].Y + this.m_normals[j].Y) * q));
  };
  ClipperLib.ClipperOffset.prototype.DoRound = function (j, k)
  {
    var a = Math.atan2(this.m_sinA,
      this.m_normals[k].X * this.m_normals[j].X + this.m_normals[k].Y * this.m_normals[j].Y);
    var steps = ClipperLib.Cast_Int32(ClipperLib.ClipperOffset.Round(this.m_StepsPerRad * Math.abs(a)));
    var X = this.m_normals[k].X,
      Y = this.m_normals[k].Y,
      X2;
    for (var i = 0; i < steps; ++i)
    {
      this.m_destPoly.push(new ClipperLib.FPoint(
        this.m_srcPoly[j].X + X * this.m_delta,
        this.m_srcPoly[j].Y + Y * this.m_delta));
      X2 = X;
      X = X * this.m_cos - this.m_sin * Y;
      Y = X2 * this.m_sin + Y * this.m_cos;
    }
    this.m_destPoly.push(new ClipperLib.FPoint(
      this.m_srcPoly[j].X + this.m_normals[j].X * this.m_delta,
      this.m_srcPoly[j].Y + this.m_normals[j].Y * this.m_delta));
  };
  ClipperLib.Error = function (message)
  {
    try
    {
      throw new Error(message);
    }
    catch (err)
    {
      console.log(err.message);
      alert(err.message);
    }
  };

  // ---------------------------------
  // JS extension by Timo 2013
  ClipperLib.JS = {};
  ClipperLib.JS.AreaOfPolygon = function (poly, scale)
  {
    if (!scale) scale = 1;
    return ClipperLib.Clipper.Area(poly) / (scale * scale);
  };
  ClipperLib.JS.AreaOfPolygons = function (poly, scale)
  {
    if (!scale) scale = 1;
    var area = 0;
    for (var i = 0; i < poly.length; i++)
    {
      area += ClipperLib.Clipper.Area(poly[i]);
    }
    return area / (scale * scale);
  };
  ClipperLib.JS.BoundsOfPath = function (path, scale)
  {
    return ClipperLib.JS.BoundsOfPaths([path], scale);
  };
  ClipperLib.JS.BoundsOfPaths = function (paths, scale)
  {
    if (!scale) scale = 1;
    var bounds = ClipperLib.Clipper.GetBounds(paths);
    bounds.left /= scale;
    bounds.bottom /= scale;
    bounds.right /= scale;
    bounds.top /= scale;
    return bounds;
  };

  // Clean() joins vertices that are too near each other
  // and causes distortion to offsetted polygons without cleaning
  ClipperLib.JS.Clean = function (polygon, delta)
  {
    if (!(polygon instanceof Array)) return [];
    var isPolygons = polygon[0] instanceof Array;
    var polygon = ClipperLib.JS.Clone(polygon);
    if (typeof delta != "number" || delta === null)
    {
      ClipperLib.Error("Delta is not a number in Clean().");
      return polygon;
    }
    if (polygon.length === 0 || (polygon.length == 1 && polygon[0].length === 0) || delta < 0) return polygon;
    if (!isPolygons) polygon = [polygon];
    var k_length = polygon.length;
    var len, poly, result, d, p, j, i;
    var results = [];
    for (var k = 0; k < k_length; k++)
    {
      poly = polygon[k];
      len = poly.length;
      if (len === 0) continue;
      else if (len < 3)
      {
        result = poly;
        results.push(result);
        continue;
      }
      result = poly;
      d = delta * delta;
      //d = Math.floor(c_delta * c_delta);
      p = poly[0];
      j = 1;
      for (i = 1; i < len; i++)
      {
        if ((poly[i].X - p.X) * (poly[i].X - p.X) +
          (poly[i].Y - p.Y) * (poly[i].Y - p.Y) <= d)
          continue;
        result[j] = poly[i];
        p = poly[i];
        j++;
      }
      p = poly[j - 1];
      if ((poly[0].X - p.X) * (poly[0].X - p.X) +
        (poly[0].Y - p.Y) * (poly[0].Y - p.Y) <= d)
        j--;
      if (j < len)
        result.splice(j, len - j);
      if (result.length) results.push(result);
    }
    if (!isPolygons && results.length) results = results[0];
    else if (!isPolygons && results.length === 0) results = [];
    else if (isPolygons && results.length === 0) results = [[]];
    return results;
  }

  // Make deep copy of Polygons or Polygon
  // so that also IntPoint objects are cloned and not only referenced
  // This should be the fastest way
  ClipperLib.JS.Clone = function (polygon)
  {
    if (!(polygon instanceof Array)) return [];
    if (polygon.length === 0) return [];
    else if (polygon.length == 1 && polygon[0].length === 0) return [[]];
    var isPolygons = polygon[0] instanceof Array;
    if (!isPolygons) polygon = [polygon];
    var len = polygon.length,
      plen, i, j, result;
    var results = new Array(len);
    for (i = 0; i < len; i++)
    {
      plen = polygon[i].length;
      result = new Array(plen);
      for (j = 0; j < plen; j++)
      {
        result[j] = {
          X: polygon[i][j].X,
          Y: polygon[i][j].Y
        };
      }
      results[i] = result;
    }
    if (!isPolygons) results = results[0];
    return results;
  };

  // Removes points that doesn't affect much to the visual appearance.
  // If middle point is at or under certain distance (tolerance) of the line segment between 
  // start and end point, the middle point is removed.
  ClipperLib.JS.Lighten = function (polygon, tolerance)
  {
    if (!(polygon instanceof Array)) return [];
    if (typeof tolerance != "number" || tolerance === null)
    {
      ClipperLib.Error("Tolerance is not a number in Lighten().")
      return ClipperLib.JS.Clone(polygon);
    }
    if (polygon.length === 0 || (polygon.length == 1 && polygon[0].length === 0) || tolerance < 0)
    {
      return ClipperLib.JS.Clone(polygon);
    }
    if (!(polygon[0] instanceof Array)) polygon = [polygon];
    var i, j, poly, k, poly2, plen, A, B, P, d, rem, addlast;
    var bxax, byay, l, ax, ay;
    var len = polygon.length;
    var toleranceSq = tolerance * tolerance;
    var results = [];
    for (i = 0; i < len; i++)
    {
      poly = polygon[i];
      plen = poly.length;
      if (plen == 0) continue;
      for (k = 0; k < 1000000; k++) // could be forever loop, but wiser to restrict max repeat count
      {
        poly2 = [];
        plen = poly.length;
        // the first have to added to the end, if first and last are not the same
        // this way we ensure that also the actual last point can be removed if needed
        if (poly[plen - 1].X != poly[0].X || poly[plen - 1].Y != poly[0].Y)
        {
          addlast = 1;
          poly.push(
          {
            X: poly[0].X,
            Y: poly[0].Y
          });
          plen = poly.length;
        }
        else addlast = 0;
        rem = []; // Indexes of removed points
        for (j = 0; j < plen - 2; j++)
        {
          A = poly[j]; // Start point of line segment
          P = poly[j + 1]; // Middle point. This is the one to be removed.
          B = poly[j + 2]; // End point of line segment
          ax = A.X;
          ay = A.Y;
          bxax = B.X - ax;
          byay = B.Y - ay;
          if (bxax !== 0 || byay !== 0) // To avoid Nan, when A==P && P==B. And to avoid peaks (A==B && A!=P), which have lenght, but not area.
          {
            l = ((P.X - ax) * bxax + (P.Y - ay) * byay) / (bxax * bxax + byay * byay);
            if (l > 1)
            {
              ax = B.X;
              ay = B.Y;
            }
            else if (l > 0)
            {
              ax += bxax * l;
              ay += byay * l;
            }
          }
          bxax = P.X - ax;
          byay = P.Y - ay;
          d = bxax * bxax + byay * byay;
          if (d <= toleranceSq)
          {
            rem[j + 1] = 1;
            j++; // when removed, transfer the pointer to the next one
          }
        }
        // add all unremoved points to poly2
        poly2.push(
        {
          X: poly[0].X,
          Y: poly[0].Y
        });
        for (j = 1; j < plen - 1; j++)
          if (!rem[j]) poly2.push(
          {
            X: poly[j].X,
            Y: poly[j].Y
          });
        poly2.push(
        {
          X: poly[plen - 1].X,
          Y: poly[plen - 1].Y
        });
        // if the first point was added to the end, remove it
        if (addlast) poly.pop();
        // break, if there was not anymore removed points
        if (!rem.length) break;
        // else continue looping using poly2, to check if there are points to remove
        else poly = poly2;
      }
      plen = poly2.length;
      // remove duplicate from end, if needed
      if (poly2[plen - 1].X == poly2[0].X && poly2[plen - 1].Y == poly2[0].Y)
      {
        poly2.pop();
      }
      if (poly2.length > 2) // to avoid two-point-polygons
        results.push(poly2);
    }
    if (!polygon[0] instanceof Array) results = results[0];
    if (typeof (results) == "undefined") results = [[]];
    return results;
  }
  ClipperLib.JS.PerimeterOfPath = function (path, closed, scale)
  {
    if (typeof (path) == "undefined") return 0;
    var sqrt = Math.sqrt;
    var perimeter = 0.0;
    var p1, p2, p1x = 0.0,
      p1y = 0.0,
      p2x = 0.0,
      p2y = 0.0;
    var j = path.length;
    if (j < 2) return 0;
    if (closed)
    {
      path[j] = path[0];
      j++;
    }
    while (--j)
    {
      p1 = path[j];
      p1x = p1.X;
      p1y = p1.Y;
      p2 = path[j - 1];
      p2x = p2.X;
      p2y = p2.Y;
      perimeter += sqrt((p1x - p2x) * (p1x - p2x) + (p1y - p2y) * (p1y - p2y));
    }
    if (closed) path.pop();
    return perimeter / scale;
  };
  ClipperLib.JS.PerimeterOfPaths = function (paths, closed, scale)
  {
    if (!scale) scale = 1;
    var perimeter = 0;
    for (var i = 0; i < paths.length; i++)
    {
      perimeter += ClipperLib.JS.PerimeterOfPath(paths[i], closed, scale);
    }
    return perimeter;
  };
  ClipperLib.JS.ScaleDownPath = function (path, scale)
  {
    var i, p;
    if (!scale) scale = 1;
    i = path.length;
    while (i--)
    {
      p = path[i];
      p.X = p.X / scale;
      p.Y = p.Y / scale;
    }
  };
  ClipperLib.JS.ScaleDownPaths = function (paths, scale)
  {
    var i, j, p, round = Math.round;
    if (!scale) scale = 1;
    i = paths.length;
    while (i--)
    {
      j = paths[i].length;
      while (j--)
      {
        p = paths[i][j];
        p.X = p.X / scale;
        p.Y = p.Y / scale;
      }
    }
  };
  ClipperLib.JS.ScaleUpPath = function (path, scale)
  {
    var i, p, round = Math.round;
    if (!scale) scale = 1;
    i = path.length;
    while (i--)
    {
      p = path[i];
      p.X = round(p.X * scale);
      p.Y = round(p.Y * scale);
    }
  };
  ClipperLib.JS.ScaleUpPaths = function (paths, scale)
  {
    var i, j, p, round = Math.round;
    if (!scale) scale = 1;
    i = paths.length;
    while (i--)
    {
      j = paths[i].length;
      while (j--)
      {
        p = paths[i][j];
        p.X = round(p.X * scale);
        p.Y = round(p.Y * scale);
      }
    }
  };
  ClipperLib.ExPolygons = function ()
  {
    return [];
  }
  ClipperLib.ExPolygon = function ()
  {
    this.outer = null;
    this.holes = null;
  };

  ClipperLib.JS.AddOuterPolyNodeToExPolygons = function (polynode, expolygons)
  {
    var ep = new ClipperLib.ExPolygon();
    ep.outer = polynode.Contour();
    var childs = polynode.Childs();
    var ilen = childs.length;
    ep.holes = new Array(ilen);
    var node, n, i, j, childs2, jlen;
    for (i = 0; i < ilen; i++)
    {
      node = childs[i];
      ep.holes[i] = node.Contour();
      //Add outer polygons contained by (nested within) holes ...
      for (j = 0, childs2 = node.Childs(), jlen = childs2.length; j < jlen; j++)
      {
        n = childs2[j];
        ClipperLib.JS.AddOuterPolyNodeToExPolygons(n, expolygons);
      }
    }
    expolygons.push(ep);
  };
  ClipperLib.JS.ExPolygonsToPaths = function (expolygons)
  {
    var a, i, alen, ilen;
    var paths = new ClipperLib.Paths();
    for (a = 0, alen = expolygons.length; a < alen; a++)
    {
      paths.push(expolygons[a].outer);
      for (i = 0, ilen = expolygons[a].holes.length; i < ilen; i++)
      {
        paths.push(expolygons[a].holes[i]);
      }
    }
    return paths;
  }
  ClipperLib.JS.PolyTreeToExPolygons = function (polytree)
  {
    var expolygons = new ClipperLib.ExPolygons();
    var node, i, childs, ilen;
    for (i = 0, childs = polytree.Childs(), ilen = childs.length; i < ilen; i++)
    {
      node = childs[i];
      ClipperLib.JS.AddOuterPolyNodeToExPolygons(node, expolygons);
    }
    return expolygons;
  };
})();
    
$(window).load( function () {
  "use strict";
  if (typeof (jQuery) == "undefined") alert("Failed to load jQuery. Please reload the page. If this fails repeatedly, please check the path of the jQuery library or use an own copy of jQuery.");
  else if (typeof (Raphael) == "undefined") alert("Failed to load Raphael. Please reload the page. If this fails repeatedly, please check the path of the Raphael library or use an own copy of Raphael.");
  else if (typeof (ClipperLib) == "undefined") alert("Failed to load ClipperLib. Please check that ClipperLib is loaded correctly.");
  else {
    ClipperLib_MaxSteps_original = ClipperLib.MaxSteps;
    browserg = get_browser();
    bench = new benchmark("bench");
    p = SVG.create();
    
window.widths = [0.01, 0.01, 0.01, 0.01, 10, 10, 10, 50];

    main();
  }
});
    
</script>
</body>
              
            
!

CSS

              
                
              
            
!

JS

              
                  ClipperLib.ClipperOffset.GetUnitNormal = function(pt1, pt2) {
    $('#debug').html($('#debug').html() + JSON.stringify(pt1) + ' ' + JSON.stringify(pt2) + ',<br>');
    var dx = (pt2.X - pt1.X); 
    var dy = (pt2.Y - pt1.Y);
    if ((dx == 0) && (dy == 0))
      return new ClipperLib.FPoint(0, 0);
    var dividend = pt1.Z;
    if(Math.abs(dividend) < 1e-4)
    {
        dividend = 1e-4;
        if(dividend < 0) dividend = -1e-4;
    }
    var f = dividend / Math.sqrt(dx * dx + dy * dy);

    dx *= f;
    dy *= f;
    return new ClipperLib.FPoint(dy, -dx);
  };
              
            
!
999px

Console