UNPKG

js-angusj-clipper

Version:

Polygon and line clipping and offsetting library for Javascript / Typescript - a port of Angus Johnson's clipper to WebAssembly / Asm.JS

500 lines 61.9 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; var __values = (this && this.__values) || function(o) { var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; if (m) return m.call(o); if (o && typeof o.length === "number") return { next: function () { if (o && i >= o.length) o = void 0; return { value: o && o[i++], done: !o }; } }; throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.loadNativeClipperLibInstanceAsync = exports.ClipperLibWrapper = exports.ClipperError = exports.PolyTree = exports.PolyNode = exports.PointInPolygonResult = exports.NativeClipperLibRequestedFormat = exports.NativeClipperLibLoadedFormat = exports.PolyFillType = exports.JoinType = exports.EndType = exports.ClipType = void 0; var clipFunctions_1 = require("./clipFunctions"); var ClipperError_1 = require("./ClipperError"); Object.defineProperty(exports, "ClipperError", { enumerable: true, get: function () { return ClipperError_1.ClipperError; } }); var constants_1 = require("./constants"); var enums_1 = require("./enums"); Object.defineProperty(exports, "ClipType", { enumerable: true, get: function () { return enums_1.ClipType; } }); Object.defineProperty(exports, "EndType", { enumerable: true, get: function () { return enums_1.EndType; } }); Object.defineProperty(exports, "JoinType", { enumerable: true, get: function () { return enums_1.JoinType; } }); Object.defineProperty(exports, "NativeClipperLibLoadedFormat", { enumerable: true, get: function () { return enums_1.NativeClipperLibLoadedFormat; } }); Object.defineProperty(exports, "NativeClipperLibRequestedFormat", { enumerable: true, get: function () { return enums_1.NativeClipperLibRequestedFormat; } }); Object.defineProperty(exports, "PointInPolygonResult", { enumerable: true, get: function () { return enums_1.PointInPolygonResult; } }); Object.defineProperty(exports, "PolyFillType", { enumerable: true, get: function () { return enums_1.PolyFillType; } }); var functions = require("./functions"); var offsetFunctions_1 = require("./offsetFunctions"); var PolyNode_1 = require("./PolyNode"); Object.defineProperty(exports, "PolyNode", { enumerable: true, get: function () { return PolyNode_1.PolyNode; } }); var PolyTree_1 = require("./PolyTree"); Object.defineProperty(exports, "PolyTree", { enumerable: true, get: function () { return PolyTree_1.PolyTree; } }); /** * A wrapper for the Native Clipper Library instance with all the operations available. */ var ClipperLibWrapper = /** @class */ (function () { /** * Internal constructor. Use loadNativeClipperLibInstanceAsync instead. * * @param instance * @param format */ function ClipperLibWrapper(instance, format) { this.format = format; this.instance = instance; } /** * Performs a polygon clipping (boolean) operation, returning the resulting Paths or throwing an error if failed. * * The solution parameter in this case is a Paths or PolyTree structure. The Paths structure is simpler than the PolyTree structure. Because of this it is * quicker to populate and hence clipping performance is a little better (it's roughly 10% faster). However, the PolyTree data structure provides more * information about the returned paths which may be important to users. Firstly, the PolyTree structure preserves nested parent-child polygon relationships * (ie outer polygons owning/containing holes and holes owning/containing other outer polygons etc). Also, only the PolyTree structure can differentiate * between open and closed paths since each PolyNode has an IsOpen property. (The Path structure has no member indicating whether it's open or closed.) * For this reason, when open paths are passed to a Clipper object, the user must use a PolyTree object as the solution parameter, otherwise an exception * will be raised. * * When a PolyTree object is used in a clipping operation on open paths, two ancilliary functions have been provided to quickly separate out open and * closed paths from the solution - OpenPathsFromPolyTree and ClosedPathsFromPolyTree. PolyTreeToPaths is also available to convert path data to a Paths * structure (irrespective of whether they're open or closed). * * There are several things to note about the solution paths returned: * - they aren't in any specific order * - they should never overlap or be self-intersecting (but see notes on rounding) * - holes will be oriented opposite outer polygons * - the solution fill type can be considered either EvenOdd or NonZero since it will comply with either filling rule * - polygons may rarely share a common edge (though this is now very rare as of version 6) * * @param params - clipping operation data * @return {Paths} - the resulting Paths. */ ClipperLibWrapper.prototype.clipToPaths = function (params) { return (0, clipFunctions_1.clipToPaths)(this.instance, params); }; /** * Performs a polygon clipping (boolean) operation, returning the resulting PolyTree or throwing an error if failed. * * The solution parameter in this case is a Paths or PolyTree structure. The Paths structure is simpler than the PolyTree structure. Because of this it is * quicker to populate and hence clipping performance is a little better (it's roughly 10% faster). However, the PolyTree data structure provides more * information about the returned paths which may be important to users. Firstly, the PolyTree structure preserves nested parent-child polygon relationships * (ie outer polygons owning/containing holes and holes owning/containing other outer polygons etc). Also, only the PolyTree structure can differentiate * between open and closed paths since each PolyNode has an IsOpen property. (The Path structure has no member indicating whether it's open or closed.) * For this reason, when open paths are passed to a Clipper object, the user must use a PolyTree object as the solution parameter, otherwise an exception * will be raised. * * When a PolyTree object is used in a clipping operation on open paths, two ancilliary functions have been provided to quickly separate out open and * closed paths from the solution - OpenPathsFromPolyTree and ClosedPathsFromPolyTree. PolyTreeToPaths is also available to convert path data to a Paths * structure (irrespective of whether they're open or closed). * * There are several things to note about the solution paths returned: * - they aren't in any specific order * - they should never overlap or be self-intersecting (but see notes on rounding) * - holes will be oriented opposite outer polygons * - the solution fill type can be considered either EvenOdd or NonZero since it will comply with either filling rule * - polygons may rarely share a common edge (though this is now very rare as of version 6) * * @param params - clipping operation data * @return {PolyTree} - the resulting PolyTree. */ ClipperLibWrapper.prototype.clipToPolyTree = function (params) { return (0, clipFunctions_1.clipToPolyTree)(this.instance, params); }; /** * Performs a polygon offset operation, returning the resulting Paths or undefined if failed. * * This method encapsulates the process of offsetting (inflating/deflating) both open and closed paths using a number of different join types * and end types. * * Preconditions for offsetting: * 1. The orientations of closed paths must be consistent such that outer polygons share the same orientation, and any holes have the opposite orientation * (ie non-zero filling). Open paths must be oriented with closed outer polygons. * 2. Polygons must not self-intersect. * * Limitations: * When offsetting, small artefacts may appear where polygons overlap. To avoid these artefacts, offset overlapping polygons separately. * * @param params - offset operation params * @return {Paths|undefined} - the resulting Paths or undefined if failed. */ ClipperLibWrapper.prototype.offsetToPaths = function (params) { return (0, offsetFunctions_1.offsetToPaths)(this.instance, params); }; /** * Performs a polygon offset operation, returning the resulting PolyTree or undefined if failed. * * This method encapsulates the process of offsetting (inflating/deflating) both open and closed paths using a number of different join types * and end types. * * Preconditions for offsetting: * 1. The orientations of closed paths must be consistent such that outer polygons share the same orientation, and any holes have the opposite orientation * (ie non-zero filling). Open paths must be oriented with closed outer polygons. * 2. Polygons must not self-intersect. * * Limitations: * When offsetting, small artefacts may appear where polygons overlap. To avoid these artefacts, offset overlapping polygons separately. * * @param params - offset operation params * @return {PolyTree|undefined} - the resulting PolyTree or undefined if failed. */ ClipperLibWrapper.prototype.offsetToPolyTree = function (params) { return (0, offsetFunctions_1.offsetToPolyTree)(this.instance, params); }; //noinspection JSMethodCanBeStatic /** * This function returns the area of the supplied polygon. It's assumed that the path is closed and does not self-intersect. Depending on orientation, * this value may be positive or negative. If Orientation is true, then the area will be positive and conversely, if Orientation is false, then the * area will be negative. * * @param path - The path * @return {number} - Area */ ClipperLibWrapper.prototype.area = function (path) { return functions.area(path); }; /** * Removes vertices: * - 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) * - that are within the specified distance of an adjacent vertex * - that are within the specified distance of a semi-adjacent vertex together with their out-lying vertices * * Vertices are semi-adjacent when they are separated by a single (out-lying) vertex. * * The distance parameter's default value is approximately √2 so that a vertex will be removed when adjacent or semi-adjacent vertices having their * corresponding X and Y coordinates differing by no more than 1 unit. (If the egdes are semi-adjacent the out-lying vertex will be removed too.) * * @param path - The path to clean * @param distance - How close points need to be before they are cleaned * @return {Path} - The cleaned path */ ClipperLibWrapper.prototype.cleanPolygon = function (path, distance) { if (distance === void 0) { distance = 1.1415; } return functions.cleanPolygon(this.instance, path, distance); }; /** * Removes vertices: * - 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) * - that are within the specified distance of an adjacent vertex * - that are within the specified distance of a semi-adjacent vertex together with their out-lying vertices * * Vertices are semi-adjacent when they are separated by a single (out-lying) vertex. * * The distance parameter's default value is approximately √2 so that a vertex will be removed when adjacent or semi-adjacent vertices having their * corresponding X and Y coordinates differing by no more than 1 unit. (If the egdes are semi-adjacent the out-lying vertex will be removed too.) * * @param paths - The paths to clean * @param distance - How close points need to be before they are cleaned * @return {Paths} - The cleaned paths */ ClipperLibWrapper.prototype.cleanPolygons = function (paths, distance) { if (distance === void 0) { distance = 1.1415; } return functions.cleanPolygons(this.instance, paths, distance); }; //noinspection JSMethodCanBeStatic /** * This function filters out open paths from the PolyTree structure and returns only closed paths in a Paths structure. * * @param polyTree * @return {Paths} */ ClipperLibWrapper.prototype.closedPathsFromPolyTree = function (polyTree) { return functions.closedPathsFromPolyTree(polyTree); }; /** * Minkowski Difference is performed by subtracting each point in a polygon from the set of points in an open or closed path. A key feature of Minkowski * Difference is that when it's applied to two polygons, the resulting polygon will contain the coordinate space origin whenever the two polygons touch or * overlap. (This function is often used to determine when polygons collide.) * * @param poly1 * @param poly2 * @return {Paths} */ ClipperLibWrapper.prototype.minkowskiDiff = function (poly1, poly2) { return functions.minkowskiDiff(this.instance, poly1, poly2); }; /** * Minkowski Addition is performed by adding each point in a polygon 'pattern' to the set of points in an open or closed path. The resulting polygon * (or polygons) defines the region that the 'pattern' would pass over in moving from the beginning to the end of the 'path'. * * @param pattern * @param path * @param pathIsClosed * @return {Paths} */ ClipperLibWrapper.prototype.minkowskiSumPath = function (pattern, path, pathIsClosed) { return functions.minkowskiSumPath(this.instance, pattern, path, pathIsClosed); }; /** * Minkowski Addition is performed by adding each point in a polygon 'pattern' to the set of points in an open or closed path. The resulting polygon * (or polygons) defines the region that the 'pattern' would pass over in moving from the beginning to the end of the 'path'. * * @param pattern * @param paths * @param pathIsClosed * @return {Paths} */ ClipperLibWrapper.prototype.minkowskiSumPaths = function (pattern, paths, pathIsClosed) { return functions.minkowskiSumPaths(this.instance, pattern, paths, pathIsClosed); }; //noinspection JSMethodCanBeStatic /** * This function filters out closed paths from the PolyTree structure and returns only open paths in a Paths structure. * * @param polyTree * @return {ReadonlyPath[]} */ ClipperLibWrapper.prototype.openPathsFromPolyTree = function (polyTree) { return functions.openPathsFromPolyTree(polyTree); }; //noinspection JSMethodCanBeStatic /** * Orientation is only important to closed paths. Given that vertices are declared in a specific order, orientation refers to the direction (clockwise or * counter-clockwise) that these vertices progress around a closed path. * * Orientation is also dependent on axis direction: * - On Y-axis positive upward displays, orientation will return true if the polygon's orientation is counter-clockwise. * - On Y-axis positive downward displays, orientation will return true if the polygon's orientation is clockwise. * * Notes: * - Self-intersecting polygons have indeterminate orientations in which case this function won't return a meaningful value. * - The majority of 2D graphic display libraries (eg GDI, GDI+, XLib, Cairo, AGG, Graphics32) and even the SVG file format have their coordinate origins * at the top-left corner of their respective viewports with their Y axes increasing downward. However, some display libraries (eg Quartz, OpenGL) have their * coordinate origins undefined or in the classic bottom-left position with their Y axes increasing upward. * - For Non-Zero filled polygons, the orientation of holes must be opposite that of outer polygons. * - For closed paths (polygons) in the solution returned by the clip method, their orientations will always be true for outer polygons and false * for hole polygons (unless the reverseSolution property has been enabled). * * @param path - Path * @return {boolean} */ ClipperLibWrapper.prototype.orientation = function (path) { return functions.orientation(path); }; //noinspection JSMethodCanBeStatic /** * Returns PointInPolygonResult.Outside when false, PointInPolygonResult.OnBoundary when point is on poly and PointInPolygonResult.Inside when point is in * poly. * * It's assumed that 'poly' is closed and does not self-intersect. * * @param point * @param path * @return {PointInPolygonResult} */ ClipperLibWrapper.prototype.pointInPolygon = function (point, path) { return functions.pointInPolygon(point, path); }; //noinspection JSMethodCanBeStatic /** * This function converts a PolyTree structure into a Paths structure. * * @param polyTree * @return {Paths} */ ClipperLibWrapper.prototype.polyTreeToPaths = function (polyTree) { return functions.polyTreeToPaths(polyTree); }; //noinspection JSMethodCanBeStatic /** * Reverses the vertex order (and hence orientation) in the specified path. * * @param path - Path to reverse, which gets overwritten rather than copied */ ClipperLibWrapper.prototype.reversePath = function (path) { functions.reversePath(path); }; //noinspection JSMethodCanBeStatic /** * Reverses the vertex order (and hence orientation) in each contained path. * * @param paths - Paths to reverse, which get overwritten rather than copied */ ClipperLibWrapper.prototype.reversePaths = function (paths) { functions.reversePaths(paths); }; /** * Removes self-intersections from the supplied polygon (by performing a boolean union operation using the nominated PolyFillType). * Polygons with non-contiguous duplicate vertices (ie 'touching') will be split into two polygons. * * Note: There's currently no guarantee that polygons will be strictly simple since 'simplifying' is still a work in progress. * * @param path * @param fillType * @return {Paths} - The solution */ ClipperLibWrapper.prototype.simplifyPolygon = function (path, fillType) { if (fillType === void 0) { fillType = enums_1.PolyFillType.EvenOdd; } return functions.simplifyPolygon(this.instance, path, fillType); }; /** * Removes self-intersections from the supplied polygons (by performing a boolean union operation using the nominated PolyFillType). * Polygons with non-contiguous duplicate vertices (ie 'vertices are touching') will be split into two polygons. * * Note: There's currently no guarantee that polygons will be strictly simple since 'simplifying' is still a work in progress. * * @param paths * @param fillType * @return {Paths} - The solution */ ClipperLibWrapper.prototype.simplifyPolygons = function (paths, fillType) { if (fillType === void 0) { fillType = enums_1.PolyFillType.EvenOdd; } return functions.simplifyPolygons(this.instance, paths, fillType); }; //noinspection JSMethodCanBeStatic /** * Scales a path by multiplying all its points by a number and then rounding them. * * @param path - Path to scale * @param scale - Scale multiplier * @return {Path} - The scaled path */ ClipperLibWrapper.prototype.scalePath = function (path, scale) { return functions.scalePath(path, scale); }; //noinspection JSMethodCanBeStatic /** * Scales all inner paths by multiplying all its points by a number and then rounding them. * * @param paths - Paths to scale * @param scale - Scale multiplier * @return {Paths} - The scaled paths */ ClipperLibWrapper.prototype.scalePaths = function (paths, scale) { return functions.scalePaths(paths, scale); }; /** * Max coordinate value (both positive and negative). */ ClipperLibWrapper.hiRange = constants_1.hiRange; return ClipperLibWrapper; }()); exports.ClipperLibWrapper = ClipperLibWrapper; var wasmModule = {}; var asmJsModule = {}; function loadModule(result, requireNativeModule, format) { return __awaiter(this, void 0, void 0, function () { var createModuleAsync, library, _a, error_1; return __generator(this, function (_b) { switch (_b.label) { case 0: // We tried this already, and it failed? if (result.error) throw result.error; // We already have a library loaded? if (result.library) return [2 /*return*/, result.library]; _b.label = 1; case 1: _b.trys.push([1, 3, , 4]); createModuleAsync = requireNativeModule(); _a = ClipperLibWrapper.bind; return [4 /*yield*/, createModuleAsync()]; case 2: library = new (_a.apply(ClipperLibWrapper, [void 0, _b.sent(), format]))(); result.library = library; return [2 /*return*/, library]; case 3: error_1 = _b.sent(); result.error = error_1; throw error_1; case 4: return [2 /*return*/]; } }); }); } function loadWasmModule() { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { return [2 /*return*/, loadModule(wasmModule, function () { return require("./wasm/clipper-wasm"); }, enums_1.NativeClipperLibLoadedFormat.Wasm)]; }); }); } function loadAsmJsModule() { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { return [2 /*return*/, loadModule(asmJsModule, function () { return require("./wasm/clipper"); }, enums_1.NativeClipperLibLoadedFormat.AsmJs)]; }); }); } /** * Asynchronously tries to load a new native instance of the clipper library to be shared across all method invocations. * * @param format - Format to load, either WasmThenAsmJs, WasmOnly or AsmJsOnly. * @return {Promise<ClipperLibWrapper>} - Promise that resolves with the wrapper instance. */ function loadNativeClipperLibInstanceAsync(format) { return __awaiter(this, void 0, void 0, function () { var loaders, loaders_1, loaders_1_1, loader; var e_1, _a; return __generator(this, function (_b) { loaders = []; if (format === enums_1.NativeClipperLibRequestedFormat.WasmWithAsmJsFallback || format === enums_1.NativeClipperLibRequestedFormat.WasmOnly) { loaders.push(loadWasmModule); } if (format === enums_1.NativeClipperLibRequestedFormat.WasmWithAsmJsFallback || format === enums_1.NativeClipperLibRequestedFormat.AsmJsOnly) { loaders.push(loadAsmJsModule); } try { for (loaders_1 = __values(loaders), loaders_1_1 = loaders_1.next(); !loaders_1_1.done; loaders_1_1 = loaders_1.next()) { loader = loaders_1_1.value; try { return [2 /*return*/, loader()]; } catch (error) { // ignore } } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (loaders_1_1 && !loaders_1_1.done && (_a = loaders_1.return)) _a.call(loaders_1); } finally { if (e_1) throw e_1.error; } } throw new ClipperError_1.ClipperError("could not load native clipper in the desired format"); }); }); } exports.loadNativeClipperLibInstanceAsync = loadNativeClipperLibInstanceAsync; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFBQSxpREFBbUc7QUFDbkcsK0NBQThDO0FBMkM1Qyw2RkEzQ08sMkJBQVksT0EyQ1A7QUExQ2QseUNBQXNDO0FBQ3RDLGlDQVFpQjtBQWFmLHlGQXBCQSxnQkFBUSxPQW9CQTtBQUNSLHdGQXBCQSxlQUFPLE9Bb0JBO0FBQ1AseUZBcEJBLGdCQUFRLE9Bb0JBO0FBRVIsNkdBckJBLG9DQUE0QixPQXFCQTtBQUM1QixnSEFyQkEsdUNBQStCLE9BcUJBO0FBQy9CLHFHQXJCQSw0QkFBb0IsT0FxQkE7QUFIcEIsNkZBakJBLG9CQUFZLE9BaUJBO0FBZmQsdUNBQXlDO0FBSXpDLHFEQUErRjtBQUcvRix1Q0FBc0M7QUFZcEMseUZBWk8sbUJBQVEsT0FZUDtBQVhWLHVDQUFzQztBQVlwQyx5RkFaTyxtQkFBUSxPQVlQO0FBZVY7O0dBRUc7QUFDSDtJQWdCRTs7Ozs7T0FLRztJQUNILDJCQUFZLFFBQWtDLEVBQUUsTUFBb0M7UUFDbEYsSUFBSSxDQUFDLE1BQU0sR0FBRyxNQUFNLENBQUM7UUFDckIsSUFBSSxDQUFDLFFBQVEsR0FBRyxRQUFRLENBQUM7SUFDM0IsQ0FBQztJQUVEOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7T0F3Qkc7SUFDSCx1Q0FBVyxHQUFYLFVBQVksTUFBa0I7UUFDNUIsT0FBTyxJQUFBLDJCQUFXLEVBQUMsSUFBSSxDQUFDLFFBQVEsRUFBRSxNQUFNLENBQUMsQ0FBQztJQUM1QyxDQUFDO0lBRUQ7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztPQXdCRztJQUNILDBDQUFjLEdBQWQsVUFBZSxNQUFrQjtRQUMvQixPQUFPLElBQUEsOEJBQWMsRUFBQyxJQUFJLENBQUMsUUFBUSxFQUFFLE1BQU0sQ0FBQyxDQUFDO0lBQy9D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LENBQUM7WUFDbkIsSUFDRSxNQUFNLEtBQUssdUNBQStCLENBQUMscUJBQXFCO2dCQUNoRSxNQUFNLEtBQUssdUNBQStCLENBQUMsUUFBUSxFQUNuRDtnQkFDQSxPQUFPLENBQUMsSUFBSSxDQUFDLGNBQWMsQ0FBQyxDQUFDO2FBQzlCO1lBQ0QsSUFDRSxNQUFNLEtBQUssdUNBQStCLENBQUMscUJBQXFCO2dCQUNoRSxNQUFNLEtBQUssdUNBQStCLENBQUMsU0FBUyxFQUNwRDtnQkFDQSxPQUFPLENBQUMsSUFBSSxDQUFDLGVBQWUsQ0FBQyxDQUFDO2FBQy9COztnQkFDRCxLQUFxQixZQUFBLFNBQUEsT0FBTyxDQUFBLHFGQUFFO29CQUFuQixNQUFNO29CQUNmLElBQUk7d0JBQ0Ysc0JBQU8sTUFBTSxFQUFFLEVBQUM7cUJBQ2pCO29CQUFDLE9BQU8sS0FBSyxFQUFFO3dCQUNkLFNBQVM7cUJBQ1Y7aUJBQ0Y7Ozs7Ozs7OztZQUNELE1BQU0sSUFBSSwyQkFBWSxDQUFDLHFEQUFxRCxDQUFDLENBQUM7OztDQUMvRTtBQXpCRCw4RUF5QkMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBDbGlwSW5wdXQsIENsaXBQYXJhbXMsIGNsaXBUb1BhdGhzLCBjbGlwVG9Qb2x5VHJlZSwgU3ViamVjdElucHV0IH0gZnJvbSBcIi4vY2xpcEZ1bmN0aW9uc1wiO1xuaW1wb3J0IHsgQ2xpcHBlckVycm9yIH0gZnJvbSBcIi4vQ2xpcHBlckVycm9yXCI7XG5pbXBvcnQgeyBoaVJhbmdlIH0gZnJvbSBcIi4vY29uc3RhbnRzXCI7XG5pbXBvcnQge1xuICBDbGlwVHlwZSxcbiAgRW5kVHlwZSxcbiAgSm9pblR5cGUsXG4gIE5hdGl2ZUNsaXBwZXJMaWJMb2FkZWRGb3JtYXQsXG4gIE5hdGl2ZUNsaXBwZXJMaWJSZXF1ZXN0ZWRGb3JtYXQsXG4gIFBvaW50SW5Qb2x5Z29uUmVzdWx0LFxuICBQb2x5RmlsbFR5cGUsXG59IGZyb20gXCIuL2VudW1zXCI7XG5pbXBvcnQgKiBhcyBmdW5jdGlvbnMgZnJvbSBcIi4vZnVuY3Rpb25zXCI7XG5pbXBvcnQgeyBJbnRQb2ludCB9IGZyb20gXCIuL0ludFBvaW50XCI7XG5pbXBvcnQgeyBJbnRSZWN0IH0gZnJvbSBcIi4vSW50UmVjdFwiO1xuaW1wb3J0IHsgTmF0aXZlQ2xpcHBlckxpYkluc3RhbmNlIH0gZnJvbSBcIi4vbmF0aXZlL05hdGl2ZUNsaXBwZXJMaWJJbnN0YW5jZVwiO1xuaW1wb3J0IHsgT2Zmc2V0SW5wdXQsIE9mZnNldFBhcmFtcywgb2Zmc2V0VG9QYXRocywgb2Zmc2V0VG9Qb2x5VHJlZSB9IGZyb20gXCIuL29mZnNldEZ1bmN0aW9uc1wiO1xuaW1wb3J0IHsgUGF0aCwgUmVhZG9ubHlQYXRoIH0gZnJvbSBcIi4vUGF0aFwiO1xuaW1wb3J0IHsgUGF0aHMsIFJlYWRvbmx5UGF0aHMgfSBmcm9tIFwiLi9QYXRoc1wiO1xuaW1wb3J0IHsgUG9seU5vZGUgfSBmcm9tIFwiLi9Qb2x5Tm9kZVwiO1xuaW1wb3J0IHsgUG9seVRyZWUgfSBmcm9tIFwiLi9Qb2x5VHJlZVwiO1xuXG4vLyBleHBvcnQgdHlwZXNcbmV4cG9ydCB7XG4gIENsaXBUeXBlLFxuICBFbmRUeXBlLFxuICBKb2luVHlwZSxcbiAgUG9seUZpbGxUeXBlLFxuICBOYXRpdmVDbGlwcGVyTGliTG9hZGVkRm9ybWF0LFxuICBOYXRpdmVDbGlwcGVyTGliUmVxdWVzdGVkRm9ybWF0LFxuICBQb2ludEluUG9seWdvblJlc3VsdCxcbiAgUG9seU5vZGUsXG4gIFBvbHlUcmVlLFxuICBJbnRQb2ludCxcbiAgSW50UmVjdCxcbiAgUGF0aCxcbiAgUmVhZG9ubHlQYXRoLFxuICBQYXRocyxcbiAgUmVhZG9ubHlQYXRocyxcbiAgQ2xpcElucHV0LFxuICBDbGlwUGFyYW1zLFxuICBTdWJqZWN0SW5wdXQsXG4gIE9mZnNldElucHV0LFxuICBPZmZzZXRQYXJhbXMsXG4gIENsaXBwZXJFcnJvcixcbn07XG5cbi8qKlxuICogQSB3cmFwcGVyIGZvciB0aGUgTmF0aXZlIENsaXBwZXIgTGlicmFyeSBpbnN0YW5jZSB3aXRoIGFsbCB0aGUgb3BlcmF0aW9ucyBhdmFpbGFibGUuXG4gKi9cbmV4cG9ydCBjbGFzcyBDbGlwcGVyTGliV3JhcHBlciB7XG4gIC8qKlxuICAgKiBNYXggY29vcmRpbmF0ZSB2YWx1ZSAoYm90aCBwb3NpdGl2ZSBhbmQgbmVnYXRpdmUpLlxuICAgKi9cbiAgc3RhdGljIHJlYWRvbmx5IGhpUmFuZ2UgPSBoaVJhbmdlO1xuXG4gIC8qKlxuICAgKiBOYXRpdmUgbGlicmFyeSBpbnN0YW5jZS5cbiAgICovXG4gIHJlYWRvbmx5IGluc3RhbmNlOiBOYXRpdmVDbGlwcGVyTGliSW5zdGFuY2U7XG5cbiAgLyoqXG4gICAqIE5hdGl2ZSBsaWJyYXJ5IGZvcm1hdC5cbiAgICovXG4gIHJlYWRvbmx5IGZvcm1hdDogTmF0aXZlQ2xpcHBlckxpYkxvYWRlZEZvcm1hdDtcblxuICAvKipcbiAgICogSW50ZXJuYWwgY29uc3RydWN0b3IuIFVzZSBsb2FkTmF0aXZlQ2xpcHBlckxpYkluc3RhbmNlQXN5bmMgaW5zdGVhZC5cbiAgICpcbiAgICogQHBhcmFtIGluc3RhbmNlXG4gICAqIEBwYXJhbSBmb3JtYXRcbiAgICovXG4gIGNvbnN0cnVjdG9yKGluc3RhbmNlOiBOYXRpdmVDbGlwcGVyTGliSW5zdGFuY2UsIGZvcm1hdDogTmF0aXZlQ2xpcHBlckxpYkxvYWRlZEZvcm1hdCkge1xuICAgIHRoaXMuZm9ybWF0ID0gZm9ybWF0O1xuICAgIHRoaXMuaW5zdGFuY2UgPSBpbnN0YW5jZTtcbiAgfVxuXG4gIC8qKlxuICAgKiBQZXJmb3JtcyBhIHBvbHlnb24gY2xpcHBpbmcgKGJvb2xlYW4pIG9wZXJhdGlvbiwgcmV0dXJuaW5nIHRoZSByZXN1bHRpbmcgUGF0aHMgb3IgdGhyb3dpbmcgYW4gZXJyb3IgaWYgZmFpbGVkLlxuICAgKlxuICAgKiBUaGUgc29sdXRpb24gcGFyYW1ldGVyIGluIHRoaXMgY2FzZSBpcyBhIFBhdGhzIG9yIFBvbHlUcmVlIHN0cnVjdHVyZS4gVGhlIFBhdGhzIHN0cnVjdHVyZSBpcyBzaW1wbGVyIHRoYW4gdGhlIFBvbHlUcmVlIHN0cnVjdHVyZS4gQmVjYXVzZSBvZiB0aGlzIGl0IGlzXG4gICAqIHF1aWNrZXIgdG8gcG9wdWxhdGUgYW5kIGhlbmNlIGNsaXBwaW5nIHBlcmZvcm1hbmNlIGlzIGEgbGl0dGxlIGJldHRlciAoaXQncyByb3VnaGx5IDEwJSBmYXN0ZXIpLiBIb3dldmVyLCB0aGUgUG9seVRyZWUgZGF0YSBzdHJ1Y3R1cmUgcHJvdmlkZXMgbW9yZVxuICAgKiBpbmZvcm1hdGlvbiBhYm91dCB0aGUgcmV0dXJuZWQgcGF0aHMgd2hpY2ggbWF5IGJlIGltcG9ydGFudCB0byB1c2Vycy4gRmlyc3RseSwgdGhlIFBvbHlUcmVlIHN0cnVjdHVyZSBwcmVzZXJ2ZXMgbmVzdGVkIHBhcmVudC1jaGlsZCBwb2x5Z29uIHJlbGF0aW9uc2hpcHNcbiAgICogKGllIG91dGVyIHBvbHlnb25zIG93bmluZy9jb250YWluaW5nIGhvbGVzIGFuZCBob2xlcyBvd25pbmcvY29udGFpbmluZyBvdGhlciBvdXRlciBwb2x5Z29ucyBldGMpLiBBbHNvLCBvbmx5IHRoZSBQb2x5VHJlZSBzdHJ1Y3R1cmUgY2FuIGRpZmZlcmVudGlhdGVcbiAgICogYmV0d2VlbiBvcGVuIGFuZCBjbG9zZWQgcGF0aHMgc2luY2UgZWFjaCBQb2x5Tm9kZSBoYXMgYW4gSXNPcGVuIHByb3BlcnR5LiAoVGhlIFBhdGggc3RydWN0dXJlIGhhcyBubyBtZW1iZXIgaW5kaWNhdGluZyB3aGV0aGVyIGl0J3Mgb3BlbiBvciBjbG9zZWQuKVxuICAgKiBGb3IgdGhpcyByZWFzb24sIHdoZW4gb3BlbiBwYXRocyBhcmUgcGFzc2VkIHRvIGEgQ2xpcHBlciBvYmplY3QsIHRoZSB1c2VyIG11c3QgdXNlIGEgUG9seVRyZWUgb2JqZWN0IGFzIHRoZSBzb2x1dGlvbiBwYXJhbWV0ZXIsIG90aGVyd2lzZSBhbiBleGNlcHRpb25cbiAgICogd2lsbCBiZSByYWlzZWQuXG4gICAqXG4gICAqIFdoZW4gYSBQb2x5VHJlZSBvYmplY3QgaXMgdXNlZCBpbiBhIGNsaXBwaW5nIG9wZXJhdGlvbiBvbiBvcGVuIHBhdGhzLCB0d28gYW5jaWxsaWFyeSBmdW5jdGlvbnMgaGF2ZSBiZWVuIHByb3ZpZGVkIHRvIHF1aWNrbHkgc2VwYXJhdGUgb3V0IG9wZW4gYW5kXG4gICAqIGNsb3NlZCBwYXRocyBmcm9tIHRoZSBzb2x1dGlvbiAtIE9wZW5QYXRoc0Zyb21Qb2x5VHJlZSBhbmQgQ2xvc2VkUGF0aHNGcm9tUG9seVRyZWUuIFBvbHlUcmVlVG9QYXRocyBpcyBhbHNvIGF2YWlsYWJsZSB0byBjb252ZXJ0IHBhdGggZGF0YSB0byBhIFBhdGhzXG4gICAqIHN0cnVjdHVyZSAoaXJyZXNwZWN0aXZlIG9mIHdoZXRoZXIgdGhleSdyZSBvcGVuIG9yIGNsb3NlZCkuXG4gICAqXG4gICAqIFRoZXJlIGFyZSBzZXZlcmFsIHRoaW5ncyB0byBub3RlIGFib3V0IHRoZSBzb2x1dGlvbiBwYXRocyByZXR1cm5lZDpcbiAgICogLSB0aGV5IGFyZW4ndCBpbiBhbnkgc3BlY2lmaWMgb3JkZXJcbiAgICogLSB0aGV5IHNob3VsZCBuZXZlciBvdmVybGFwIG9yIGJlIHNlbGYtaW50ZXJzZWN0aW5nIChidXQgc2VlIG5vdGVzIG9uIHJvdW5kaW5nKVxuICAgKiAtIGhvbGVzIHdpbGwgYmUgb3JpZW50ZWQgb3Bwb3NpdGUgb3V0ZXIgcG9seWdvbnNcbiAgICogLSB0aGUgc29sdXRpb24gZmlsbCB0eXBlIGNhbiBiZSBjb25zaWRlcmVkIGVpdGhlciBFdmVuT2RkIG9yIE5vblplcm8gc2luY2UgaXQgd2lsbCBjb21wbHkgd2l0aCBlaXRoZXIgZmlsbGluZyBydWxlXG4gICAqIC0gcG9seWdvbnMgbWF5IHJhcmVseSBzaGFyZSBhIGNvbW1vbiBlZGdlICh0aG91Z2ggdGhpcyBpcyBub3cgdmVyeSByYXJlIGFzIG9mIHZlcnNpb24gNilcbiAgICpcbiAgICogQHBhcmFtIHBhcmFtcyAtIGNsaXBwaW5nIG9wZXJhdGlvbiBkYXRhXG4gICAqIEByZXR1cm4ge1BhdGhzfSAtIHRoZSByZXN1bHRpbmcgUGF0aHMuXG4gICAqL1xuICBjbGlwVG9QYXRocyhwYXJhbXM6IENsaXBQYXJhbXMpOiBQYXRocyB7XG4gICAgcmV0dXJuIGNsaXBUb1BhdGhzKHRoaXMuaW5zdGFuY2UsIHBhcmFtcyk7XG4gIH1cblxuICAvKipcbiAgICogUGVyZm9ybXMgYSBwb2x5Z29uIGNsaXBwaW5nIChib29sZWFuKSBvcGVyYXRpb24sIHJldHVybmluZyB0aGUgcmVzdWx0aW5nIFBvbHlUcmVlIG9yIHRocm93aW5nIGFuIGVycm9yIGlmIGZhaWxlZC5cbiAgICpcbiAgICogVGhlIHNvbHV0aW9uIHBhcmFtZXRlciBpbiB0aGlzIGNhc2UgaXMgYSBQYXRocyBvciBQb2x5VHJlZSBzdHJ1Y3R1cmUuIFRoZSBQYXRocyBzdHJ1Y3R1cmUgaXMgc2ltcGxlciB0aGFuIHRoZSBQb2x5VHJlZSBzdHJ1Y3R1cmUuIEJlY2F1c2Ugb2YgdGhpcyBpdCBpc1xuICAgKiBxdWlja2VyIHRvIHBvcHVsYXRlIGFuZCBoZW5jZSBjbGlwcGluZyBwZXJmb3JtYW5jZSBpcyBhIGxpdHRsZSBiZXR0ZXIgKGl0J3Mgcm91Z2hseSAxMCUgZmFzdGVyKS4gSG93ZXZlciwgdGhlIFBvbHlUcmVlIGRhdGEgc3RydWN0dXJlIHByb3ZpZGVzIG1vcmVcbiAgICogaW5mb3JtYXRpb24gYWJvdXQgdGhlIHJldHVybmVkIHBhdGhzIHdoaWNoIG1heSBiZSBpbXBvcnRhbnQgdG8gdXNlcnMuIEZpcnN0bHksIHRoZSBQb2x5VHJlZSBzdHJ1Y3R1cmUgcHJlc2VydmVzIG5lc3RlZCBwYXJlbnQtY2hpbGQgcG9seWdvbiByZWxhdGlvbnNoaXBzXG4gICAqIChpZSBvdXRlciBwb2x5Z29ucyBvd25pbmcvY29udGFpbmluZyBob2xlcyBhbmQgaG9sZXMgb3duaW5nL2NvbnRhaW5pbmcgb3RoZXIgb3V0ZXIgcG9seWdvbnMgZXRjKS4gQWxzbywgb25seSB0aGUgUG9seVRyZWUgc3RydWN0dXJlIGNhbiBkaWZmZXJlbnRpYXRlXG4gICAqIGJldHdlZW4gb3BlbiBhbmQgY2xvc2VkIHBhdGhzIHNpbmNlIGVhY2ggUG9seU5vZGUgaGFzIGFuIElzT3BlbiBwcm9wZXJ0eS4gKFRoZSBQYXRoIHN0cnVjdHVyZSBoYXMgbm8gbWVtYmVyIGluZGljYXRpbmcgd2hldGhlciBpdCdzIG9wZW4gb3IgY2xvc2VkLilcbiAgICogRm9yIHRoaXMgcmVhc29uLCB3aGVuIG9wZW4gcGF0aHMgYXJlIHBhc3NlZCB0byBhIENsaXBwZXIgb2JqZWN0LCB0aGUgdXNlciBtdXN0IHVzZSBhIFBvbHlUcmVlIG9iamVjdCBhcyB0aGUgc29sdXRpb24gcGFyYW1ldGVyLCBvdGhlcndpc2UgYW4gZXhjZXB0aW9uXG4gICAqIHdpbGwgYmUgcmFpc2VkLlxuICAgKlxuICAgKiBXaGVuIGEgUG9seVRyZWUgb2JqZWN0IGlzIHVzZWQgaW4gYSBjbGlwcGluZyBvcGVyYXRpb24gb24gb3BlbiBwYXRocywgdHdvIGFuY2lsbGlhcnkgZnVuY3Rpb25zIGhhdmUgYmVlbiBwcm92aWRlZCB0byBxdWlja2x5IHNlcGFyYXRlIG91dCBvcGVuIGFuZFxuICAgKiBjbG9zZWQgcGF0aHMgZnJvbSB0aGUgc29sdXRpb24gLSBPcGVuUGF0aHNGcm9tUG9seVRyZWUgYW5kIENsb3NlZFBhdGhzRnJvbVBvbHlUcmVlLiBQb2x5VHJlZVRvUGF0aHMgaXMgYWxzbyBhdmFpbGFibGUgdG8gY29udmVydCBwYXRoIGRhdGEgdG8gYSBQYXRoc1xuICAgKiBzdHJ1Y3R1cmUgKGlycmVzcGVjdGl2ZSBvZiB3aGV0aGVyIHRoZXkncmUgb3BlbiBvciBjbG9zZWQpLlxuICAgKlxuICAgKiBUaGVyZSBhcmUgc2V2ZXJhbCB0aGluZ3MgdG8gbm90ZSBhYm91dCB0aGUgc29sdXRpb24gcGF0aHMgcmV0dXJuZWQ6XG4gICAqIC0gdGhleSBhcmVuJ3QgaW4gYW55IHNwZWNpZmljIG9yZGVyXG4gICAqIC0gdGhleSBzaG91bGQgbmV2ZXIgb3ZlcmxhcCBvciBiZSBzZWxmLWludGVyc2VjdGluZyAoYnV0IHNlZSBub3RlcyBvbiByb3VuZGluZylcbiAgICogLSBob2xlcyB3aWxsIGJlIG9yaWVudGVkIG9wcG9zaXRlIG91dGVyIHBvbHlnb25zXG4gICAqIC0gdGhlIHNvbHV0aW9uIGZpbGwgdHlwZSBjYW4gYmUgY29uc2lkZXJlZCBlaXRoZXIgRXZlbk9kZCBvciBOb25aZXJvIHNpbmNlIGl0IHdpbGwgY29tcGx5IHdpdGggZWl0aGVyIGZpbGxpbmcgcnVsZVxuICAgKiAtIHBvbHlnb25zIG1heSByYXJlbHkgc2hhcmUgYSBjb21tb24gZWRnZSAodGhvdWdoIHRoaXMgaXMgbm93IHZlcnkgcmFyZSBhcyBvZiB2ZXJzaW9uIDYpXG4gICAqXG4gICAqIEBwYXJhbSBwYXJhbXMgLSBjbGlwcGluZyBvcGVyYXRpb24gZGF0YVxuICAgKiBAcmV0dXJuIHtQb2x5VHJlZX0gLSB0aGUgcmVzdWx0aW5nIFBvbHlUcmVlLlxuICAgKi9cbiAgY2xpcFRvUG9seVRyZWUocGFyYW1zOiBDbGlwUGFyYW1zKTogUG9seVRyZWUge1xuICAgIHJldHVybiBjbGlwVG9Qb2x5VHJlZSh0aGlzLmluc3RhbmNlLCBwYXJhbXMpO1xuICB9XG5cbiAgLyoqXG4gICAqIFBlcmZvcm1zIGEgcG9seWdvbiBvZmZzZXQgb3BlcmF0aW9uLCByZXR1cm5pbmcgdGhlIHJlc3VsdGluZyBQYXRocyBvciB1bmRlZmluZWQgaWYgZmFpbGVkLlxuICAgKlxuICAgKiBUaGlzIG1ldGhvZCBlbmNhcHN1bGF0ZXMgdGhlIHByb2Nlc3Mgb2Ygb2Zmc2V0dGluZyAoaW5mbGF0aW5nL2RlZmxhdGluZykgYm90aCBvcGVuIGFuZCBjbG9zZWQgcGF0aHMgdXNpbmcgYSBudW1iZXIgb2YgZGlmZmVyZW50IGpvaW4gdHlwZXNcbiAgICogYW5kIGVuZCB0eXBlcy5cbiAgICpcbiAgICogUHJlY29uZGl0aW9ucyBmb3Igb2Zmc2V0dGluZzpcbiAgICogMS4gVGhlIG9yaWVudGF0aW9ucyBvZiBjbG9zZWQgcGF0aHMgbXVzdCBiZSBjb25zaXN0ZW50IHN1Y2ggdGhhdCBvdXRlciBwb2x5Z29ucyBzaGFyZSB0aGUgc2FtZSBvcmllbnRhdGlvbiwgYW5kIGFueSBob2xlcyBoYXZlIHRoZSBvcHBvc2l0ZSBvcmllbnRhdGlvblxuICAgKiAoaWUgbm9uLXplcm8gZmlsbGluZykuIE9wZW4gcGF0aHMgbXVzdCBiZSBvcmllbnRlZCB3aXRoIGNsb3NlZCBvdXRlciBwb2x5Z29ucy5cbiAgICogMi4gUG9seWdvbnMgbXVzdCBub3Qgc2VsZi1pbnRlcnNlY3QuXG4gICAqXG4gICAqIExpbWl0YXRpb25zOlxuICAgKiBXaGVuIG9mZnNldHRpbmcsIHNtYWxsIGFydGVmYWN0cyBtYXkgYXBwZWFyIHdoZXJlIHBvbHlnb25zIG92ZXJsYXAuIFRvIGF2b2lkIHRoZXNlIGFydGVmYWN0cywgb2Zmc2V0IG92ZXJsYXBwaW5nIHBvbHlnb25zIHNlcGFyYXRlbHkuXG4gICAqXG4gICAqIEBwYXJhbSBwYXJhbXMgLSBvZmZzZXQgb3BlcmF0aW9uIHBhcmFtc1xuICAgKiBAcmV0dXJuIHtQYXRoc3x1bmRlZmluZWR9IC0gdGhlIHJlc3VsdGluZyBQYXRocyBvciB1bmRlZmluZWQgaWYgZmFpbGVkLlxuICAgKi9cbiAgb2Zmc2V0VG9QYXRocyhwYXJhbXM6IE9mZnNldFBhcmFtcyk6IFBhdGhzIHwgdW5kZWZpbmVkIHtcbiAgICByZXR1cm4gb2Zmc2V0VG9QYXRocyh0aGlzLmluc3RhbmNlLCBwYXJhbXMpO1xuICB9XG5cbiAgLyoqXG4gICAqIFBlcmZvcm1zIGEgcG9seWdvbiBvZmZzZXQgb3BlcmF0aW9uLCByZXR1cm5pbmcgdGhlIHJlc3VsdGluZyBQb2x5VHJlZSBvciB1bmRlZmluZWQgaWYgZmFpbGVkLlxuICAgKlxuICAgKiBUaGlzIG1ldGhvZCBlbmNhcHN1bGF0ZXMgdGhlIHByb2Nlc3Mgb2Ygb2Zmc2V0dGluZyAoaW5mbGF0aW5nL2RlZmxhdGluZykgYm90aCBvcGVuIGFuZCBjbG9zZWQgcGF0aHMgdXNpbmcgYSBudW1iZXIgb2YgZGlmZmVyZW50IGpvaW4gdHlwZXNcbiAgICogYW5kIGVuZCB0eXBlcy5cbiAgICpcbiAgICogUHJlY29uZGl0aW9ucyBmb3Igb2Zmc2V0dGluZzpcbiAgICogMS4gVGhlIG9yaWVudGF0aW9ucyBvZiBjbG9zZWQgcGF0aHMgbXVzdCBiZSBjb25zaXN0ZW50IHN1Y2ggdGhhdCBvdXRlciBwb2x5Z29ucyBzaGFyZSB0aGUgc2FtZSBvcmllbnRhdGlvbiwgYW5kIGFueSBob2xlcyBoYXZlIHRoZSBvcHBvc2l0ZSBvcmllbnRhdGlvblxuICAgKiAoaWUgbm9uLXplcm8gZmlsbGluZykuIE9wZW4gcGF0aHMgbXVzdCBiZSBvcmllbnRlZCB3aXRoIGNsb3NlZCBvdXRlciBwb2x5Z29ucy5cbiAgICogMi4gUG9seWdvbnMgbXVzdCBub3Qgc2VsZi1pbnRlcnNlY3QuXG4gICAqXG4gICAqIExpbWl0YXRpb25zOlxuICAgKiBXaGVuIG9mZnNldHRpbmcsIHNtYWxsIGFydGVmYWN0cyBtYXkgYXBwZWFyIHdoZXJlIHBvbHlnb25zIG92ZXJsYXAuIFRvIGF2b2lkIHRoZXNlIGFydGVmYWN0cywgb2Zmc2V0IG92ZXJsYXBwaW5nIHBvbHlnb25zIHNlcGFyYXRlbHkuXG4gICAqXG4gICAqIEBwYXJhbSBwYXJhbXMgLSBvZmZzZXQgb3BlcmF0aW9uIHBhcmFtc1xuICAgKiBAcmV0dXJuIHtQb2x5VHJlZXx1bmRlZmluZWR9IC0gdGhlIHJlc3VsdGluZyBQb2x5VHJlZSBvciB1bmRlZmluZWQgaWYgZmFpbGVkLlxuICAgKi9cbiAgb2Zmc2V0VG9Qb2x5VHJlZShwYXJhbXM6IE9mZnNldFBhcmFtcyk6IFBvbHlUcmVlIHwgdW5kZWZpbmVkIHtcbiAgICByZXR1cm4gb2Zmc2V0VG9Qb2x5VHJlZSh0aGlzLmluc3RhbmNlLCBwYXJhbXMpO1xuICB9XG5cbiAgLy9ub2luc3BlY3Rpb24gSlNNZXRob2RDYW5CZVN0YXRpY1xuICAvKipcbiAgICogVGhpcyBmdW5jdGlvbiByZXR1cm5zIHRoZSBhcmVhIG9mIHRoZSBzdXBwbGllZCBwb2x5Z29uLiBJdCdzIGFzc3VtZWQgdGhhdCB0aGUgcGF0aCBpcyBjbG9zZWQgYW5kIGRvZXMgbm90IHNlbGYtaW50ZXJzZWN0LiBEZXBlbmRpbmcgb24gb3JpZW50YXRpb24sXG4gICAqIHRoaXMgdmFsdWUgbWF5IGJlIHBvc2l0aXZlIG9yIG5lZ2F0aXZlLiBJZiBPcmllbnRhdGlvbiBpcyB0cnVlLCB0aGVuIHRoZSBhcmVhIHdpbGwgYmUgcG9zaXRpdmUgYW5kIGNvbnZlcnNlbHksIGlmIE9yaWVudGF0aW9uIGlzIGZhbHNlLCB0aGVuIHRoZVxuICAgKiBhcmVhIHdpbGwgYmUgbmVnYXRpdmUuXG4gICAqXG4gICAqIEBwYXJhbSBwYXRoIC0gVGhlIHBhdGhcbiAgICogQHJldHVybiB7bnVtYmVyfSAtIEFyZWFcbiAgICovXG4gIGFyZWEocGF0aDogUmVhZG9ubHlQYXRoKTogbnVtYmVyIHtcbiAgICByZXR1cm4gZnVuY3Rpb25zLmFyZWEocGF0aCk7XG4gIH1cblxuICAvKipcbiAgICogUmVtb3ZlcyB2ZXJ0aWNlczpcbiAgICogLSB0aGF0IGpvaW4gY28tbGluZWFyIGVkZ2VzLCBvciBqb2luIGVkZ2VzIHRoYXQgYXJlIGFsbW9zdCBjby1saW5lYXIgKHN1Y2ggdGhhdCBpZiB0aGUgdmVydGV4IHdhcyBtb3ZlZCBubyBtb3JlIHRoYW4gdGhlIHNwZWNpZmllZCBkaXN0YW5jZSB0aGUgZWRnZXNcbiAgICogd291bGQgYmUgY28tbGluZWFyKVxuICAgKiAtIHRoYXQgYXJlIHdpdGhpbiB0aGUgc3BlY2lmaWVkIGRpc3RhbmNlIG9mIGFuIGFkamFjZW50IHZlcnRleFxuICAgKiAtIHRoYXQgYXJlIHdpdGhpbiB0aGUgc3BlY2lmaWVkIGRpc3RhbmNlIG9mIGEgc2VtaS1hZGphY2VudCB2ZXJ0ZXggdG9nZXRoZXIgd2l0aCB0aGVpciBvdXQtbHlpbmcgdmVydGljZXNcbiAgICpcbiAgICogVmVydGljZXMgYXJlIHNlbWktYWRqYWNlbnQgd2hlbiB0aGV5IGFyZSBzZXBhcmF0ZWQgYnkgYSBzaW5nbGUgKG91dC1seWluZykgdmVydGV4LlxuICAgKlxuICAgKiBUaGUgZGlzdGFuY2UgcGFyYW1ldGVyJ3MgZGVmYXVsdCB2YWx1ZSBpcyBhcHByb3hpbWF0ZWx5IOKImjIgc28gdGhhdCBhIHZlcnRleCB3aWxsIGJlIHJlbW92ZWQgd2hlbiBhZGphY2VudCBvciBzZW1pLWFkamFjZW50IHZlcnRpY2VzIGhhdmluZyB0aGVpclxuICAgKiBjb3JyZXNwb25kaW5nIFggYW5kIFkgY29vcmRpbmF0ZXMgZGlmZmVyaW5nIGJ5IG5vIG1vcmUgdGhhbiAxIHVuaXQuIChJZiB0aGUgZWdkZXMgYXJlIHNlbWktYWRqYWNlbnQgdGhlIG91dC1seWluZyB2ZXJ0ZXggd2lsbCBiZSByZW1vdmVkIHRvby4pXG4gICAqXG4gICAqIEBwYXJhbSBwYXRoIC0gVGhlIHBhdGggdG8gY2xlYW5cbiAgICogQHBhcmFtIGRpc3RhbmNlIC0gSG93IGNsb3NlIHBvaW50cyBuZWVkIHRvIGJlIGJlZm9yZSB0aGV5IGFyZSBjbGVhbmVkXG4gICAqIEByZXR1cm4ge1BhdGh9IC0gVGhlIGNsZWFuZWQgcGF0aFxuICAgKi9cbiAgY2xlYW5Qb2x5Z29uKHBhdGg6IFJlYWRvbmx5UGF0aCwgZGlzdGFuY2UgPSAxLjE0MTUpOiBQYXRoIHtcbiAgICByZXR1cm4gZnVuY3Rpb25zLmNsZWFuUG9seWdvbih0aGlzLmluc3RhbmNlLCBwYXRoLCBkaXN0YW5jZSk7XG4gIH1cblxuICAvKipcbiAgICogUmVtb3ZlcyB2ZXJ0aWNlczpcbiAgICogLSB0aGF0IGpvaW4gY28tbGluZWFyIGVkZ2VzLCBvciBqb2luIGVkZ2VzIHRoYXQgYXJlIGFsbW9zdCBjby1saW5lYXIgKHN1Y2ggdGhhdCBpZiB0aGUgdmVydGV4IHdhcyBtb3ZlZCBubyBtb3JlIHRoYW4gdGhlIHNwZWNpZmllZCBkaXN0YW5jZSB0aGUgZWRnZXNcbiAgICogd291bGQgYmUgY28tbGluZWFyKVxuICAgKiAtIHRoYXQgYXJlIHdpdGhpbiB0aGUgc3BlY2lmaWVkIGRpc3RhbmNlIG9mIGFuIGFkamFjZW50IHZlcnRleFxuICAgKiAtIHRoYXQgYXJlIHdpdGhpbiB0aGUgc3BlY2lmaWVkIGRpc3RhbmNlIG9mIGEgc2VtaS1hZGphY2VudCB2ZXJ0ZXggdG9nZXRoZXIgd2l0aCB0aGVpciBvdXQtbHlpbmcgdmVydGljZXNcbiAgICpcbiAgICogVmVydGljZXMgYXJlIHNlbWktYWRqYWNlbnQgd2hlbiB0aGV5IGFyZSBzZXBhcmF0ZWQgYnkgYSBzaW5nbGUgKG91dC1seWluZykgdmVydGV4LlxuICAgKlxuICAgKiBUaGUgZGlzdGFuY2UgcGFyYW1ldGVyJ3MgZGVmYXVsdCB2YWx1ZSBpcyBhcHByb3hpbWF0ZWx5IOKImjIgc28gdGhhdCBhIHZlcnRleCB3aWxsIGJlIHJlbW92ZWQgd2hlbiBhZGphY2VudCBvciBzZW1pLWFkamFjZW50IHZlcnRpY2VzIGhhdmluZyB0aGVpclxuICAgKiBjb3JyZXNwb25kaW5nIFggYW5kIFkgY29vcmRpbmF0ZXMgZGlmZmVyaW5nIGJ5IG5vIG1vcmUgdGhhbiAxIHVuaXQuIChJZiB0aGUgZWdkZXMgYXJlIHNlbWktYWRqYWNlbnQgdGhlIG91dC1seWluZyB2ZXJ0ZXggd2lsbCBiZSByZW1vdmVkIHRvby4pXG4gICAqXG4gICAqIEBwYXJhbSBwYXRocyAtIFRoZSBwYXRocyB0byBjbGVhblxuICAgKiBAcGFyYW0gZGlzdGFuY2UgLSBIb3cgY2xvc2UgcG9pbnRzIG5lZWQgdG8gYmUgYmVmb3JlIHRoZXkgYXJlIGNsZWFuZWRcbiAgICogQHJldHVybiB7UGF0aHN9IC0gVGhlIGNsZWFuZWQgcGF0aHNcbiAgICovXG4gIGNsZWFuUG9seWdvbnMocGF0aHM6IFJlYWRvbmx5UGF0aHMsIGRpc3RhbmNlID0gMS4xNDE1KTogUGF0aHMge1xuICAgIHJldHVybiBmdW5jdGlvbnMuY2xlYW5Qb2x5Z29ucyh0aGlzLmluc3RhbmNlLCBwYXRocywgZGlzdGFuY2UpO1xuICB9XG5cbiAgLy9ub2luc3BlY3Rpb24gSlNNZXRob2RDYW5CZVN0YXRpY1xuICAvKipcbiAgICogVGhpcyBmdW5jdGlvbiBmaWx0ZXJzIG91dCBvcGVuIHBhdGhzIGZyb20gdGhlIFBvbHlUcmVlIHN0cnVjdHVyZSBhbmQgcmV0dXJucyBvbmx5IGNsb3NlZCBwYXRocyBpbiBhIFBhdGhzIHN0cnVjdHVyZS5cbiAgICpcbiAgICogQHBhcmFtIHBvbHlUcmVlXG4gICAqIEByZXR1cm4ge1BhdGhzfVxuICAgKi9cbiAgY2xvc2VkUGF0aHNGcm9tUG9seVRyZWUocG9seVRyZWU6IFBvbHlUcmVlKTogUGF0aHMge1xuICAgIHJldHVybiBmdW5jdGlvbnMuY2xvc2VkUGF0aHNGcm9tUG9seVRyZWUocG9seVRyZWUpO1xuICB9XG5cbiAgLyoqXG4gICAqICBNaW5rb3dza2kgRGlmZmVyZW5jZSBpcyBwZXJmb3JtZWQgYnkgc3VidHJhY3RpbmcgZWFjaCBwb2ludCBpbiBhIHBvbHlnb24gZnJvbSB0aGUgc2V0IG9mIHBvaW50cyBpbiBhbiBvcGVuIG9yIGNsb3NlZCBwYXRoLiBBIGtleSBmZWF0dXJlIG9mIE1pbmtvd3NraVxuICAgKiAgRGlmZmVyZW5jZSBpcyB0aGF0IHdoZW4gaXQncyBhcHBsaWVkIHRvIHR3byBwb2x5Z29ucywgdGhlIHJlc3VsdGluZyBwb2x5Z29uIHdpbGwgY29udGFpbiB0aGUgY29vcmRpbmF0ZSBzcGFjZSBvcmlnaW4gd2hlbmV2ZXIgdGhlIHR3byBwb2x5Z29ucyB0b3VjaCBvclxuICAgKiAgb3ZlcmxhcC4gKFRoaXMgZnVuY3Rpb24gaXMgb2Z0ZW4gdXNlZCB0byBkZXRlcm1pbmUgd2hlbiBwb2x5Z29ucyBjb2xsaWRlLilcbiAgICpcbiAgICogQHBhcmFtIHBvbHkxXG4gICAqIEBwYXJhbSBwb2x5MlxuICAgKiBAcmV0dXJuIHtQYXRoc31cbiAgICovXG4gIG1pbmtvd3NraURpZmYocG9seTE6IFJlYWRvbmx5UGF0aCwgcG9seTI6IFJlYWRvbmx5UGF0aCk6IFBhdGhzIHtcbiAgICByZXR1cm4gZnVuY3Rpb25zLm1pbmtvd3NraURpZmYodGhpcy5pbnN0YW5jZSwgcG9seTEsIHBvbHkyKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBNaW5rb3dza2kgQWRkaXRpb24gaXMgcGVyZm9ybWVkIGJ5IGFkZGluZyBlYWNoI